diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 3309c63c..13fd6e0a 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -26,9 +26,11 @@ jobs: echo "Changed files:" echo "$changed_files" - + tasks=("addition" "rms" "print_bits" "check_flags" "length_lit" "quadratic" "char_changer" - "swap_ptr" "func_array" "longest" "last_of_us" "little_big" "pretty_array") + "swap_ptr" "func_array" "longest" "last_of_us" "little_big" "pretty_array" + "data_stats" "unique" "range" "minmax" "find_all" "os_overload" "easy_compare" "filter" "enum_operators" + "stack" "queue" "ring_buffer" "phasor") declare -i passed_count=0 declare -i failed_count=0 diff --git a/.github/workflows/testing_week_01.yml b/.github/workflows/testing_week_01.yml new file mode 100644 index 00000000..3ece11af --- /dev/null +++ b/.github/workflows/testing_week_01.yml @@ -0,0 +1,95 @@ +name: Testing Tasks Week 01 + +on: + workflow_dispatch: + inputs: + tasks: + description: 'Select tasks to test' + required: true + type: choice + default: 'all' + options: + - all + - addition + - rms + - print_bits + - check_flags + - length_lit + - quadratic + - char_changer + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install compiler and CMake + run: sudo apt install -y cmake build-essential g++-14 libgtest-dev libgmock-dev + + - name: Configure project + run: cmake -B build + + - name: Determine tasks to run + id: get-tasks + run: | + if [ "${{ github.event.inputs.tasks }}" = "all" ]; then + # Find all tasks + TASKS=() + for dir in 01_week/tasks/*/; do + task=$(basename "$dir") + TASKS+=("$task") + done + echo "tasks=${TASKS[*]}" >> $GITHUB_OUTPUT + else + # Используем указанную задачу + echo "tasks=${{ github.event.inputs.tasks }}" >> $GITHUB_OUTPUT + fi + + - name: Build and run tests for selected tasks + run: | + + IFS=' ' read -ra tasks <<< "${{ steps.get-tasks.outputs.tasks }}" + + declare -i passed_count=0 + declare -i failed_count=0 + declare -i task_count=0 + + echo "=== Starting tests for selected tasks ===" + + for task in "${tasks[@]}"; do + task_count+=1 + echo "=== Processing $task ===" + + if cmake --build build --target test_$task; then + echo "✅ test_$task built successfully" + + if ./build/tasks/test_$task; then + echo "✅ test_$task PASSED" + passed_count+=1 + else + echo "❌ test_$task FAILED" + failed_count+=1 + fi + else + echo "❌ test_$task build FAILED" + failed_count+=1 + fi + done + + echo "=== Test Results Summary ===" + echo "Total tasks in list: ${#tasks[@]}" + echo "Processed: $task_count" + echo "✅ Passed: $passed_count" + echo "❌ Failed: $failed_count" + + if [ $failed_count -gt 0 ]; then + echo "❌ Some tasks failed!" + exit 1 + elif [ $task_count -eq 0 ]; then + echo "No tasks were processed (no changes)" + exit 0 + else + echo "✅ All processed tasks passed!" + exit 0 + fi \ No newline at end of file diff --git a/.github/workflows/testing_week_02.yml b/.github/workflows/testing_week_02.yml new file mode 100644 index 00000000..dc37cec9 --- /dev/null +++ b/.github/workflows/testing_week_02.yml @@ -0,0 +1,94 @@ +name: Testing Tasks Week 02 + +on: + workflow_dispatch: + inputs: + tasks: + description: 'Select tasks to test' + required: true + type: choice + default: 'all' + options: + - all + - swap_ptr + - func_array + - longest + - last_of_us + - little_big + - pretty_array + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install compiler and CMake + run: sudo apt install -y cmake build-essential g++-14 libgtest-dev libgmock-dev + + - name: Configure project + run: cmake -B build + + - name: Determine tasks to run + id: get-tasks + run: | + if [ "${{ github.event.inputs.tasks }}" = "all" ]; then + # Find all tasks + TASKS=() + for dir in 02_week/tasks/*/; do + task=$(basename "$dir") + TASKS+=("$task") + done + echo "tasks=${TASKS[*]}" >> $GITHUB_OUTPUT + else + # Используем указанную задачу + echo "tasks=${{ github.event.inputs.tasks }}" >> $GITHUB_OUTPUT + fi + + - name: Build and run tests for selected tasks + run: | + + IFS=' ' read -ra tasks <<< "${{ steps.get-tasks.outputs.tasks }}" + + declare -i passed_count=0 + declare -i failed_count=0 + declare -i task_count=0 + + echo "=== Starting tests for selected tasks ===" + + for task in "${tasks[@]}"; do + task_count+=1 + echo "=== Processing $task ===" + + if cmake --build build --target test_$task; then + echo "✅ test_$task built successfully" + + if ./build/tasks/test_$task; then + echo "✅ test_$task PASSED" + passed_count+=1 + else + echo "❌ test_$task FAILED" + failed_count+=1 + fi + else + echo "❌ test_$task build FAILED" + failed_count+=1 + fi + done + + echo "=== Test Results Summary ===" + echo "Total tasks in list: ${#tasks[@]}" + echo "Processed: $task_count" + echo "✅ Passed: $passed_count" + echo "❌ Failed: $failed_count" + + if [ $failed_count -gt 0 ]; then + echo "❌ Some tasks failed!" + exit 1 + elif [ $task_count -eq 0 ]; then + echo "No tasks were processed (no changes)" + exit 0 + else + echo "✅ All processed tasks passed!" + exit 0 + fi \ No newline at end of file diff --git a/.github/workflows/testing_week_03.yml b/.github/workflows/testing_week_03.yml new file mode 100644 index 00000000..fdb0892f --- /dev/null +++ b/.github/workflows/testing_week_03.yml @@ -0,0 +1,100 @@ +name: Testing Tasks Week 03 + +on: + workflow_dispatch: + inputs: + tasks: + description: 'Select tasks to test' + required: true + type: choice + default: 'all' + options: + - all + - data_stats + - unique + - range + - minmax + - find_all + - os_overload + - easy_compare + - filter + - enum_operators + + schedule: + - cron: '59 20 19 12 *' # UTC: 20:59 = 23:59 MSK 19 December + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install compiler and CMake + run: sudo apt install -y cmake build-essential g++-14 libgtest-dev libgmock-dev + + - name: Configure project + run: cmake -B build + + - name: Determine tasks to run + id: get-tasks + run: | + if [ "${{ github.event.inputs.tasks }}" = "all" ]; then + # Find all tasks + TASKS=() + for dir in 03_week/tasks/*/; do + task=$(basename "$dir") + TASKS+=("$task") + done + echo "tasks=${TASKS[*]}" >> $GITHUB_OUTPUT + else + # Используем указанную задачу + echo "tasks=${{ github.event.inputs.tasks }}" >> $GITHUB_OUTPUT + fi + + - name: Build and run tests for selected tasks + run: | + + IFS=' ' read -ra tasks <<< "${{ steps.get-tasks.outputs.tasks }}" + + declare -i passed_count=0 + declare -i failed_count=0 + declare -i task_count=0 + + echo "=== Starting tests for selected tasks ===" + + for task in "${tasks[@]}"; do + task_count+=1 + echo "=== Processing $task ===" + + if cmake --build build --target test_$task; then + echo "✅ test_$task built successfully" + + if ./build/tasks/test_$task; then + echo "✅ test_$task PASSED" + passed_count+=1 + else + echo "❌ test_$task FAILED" + failed_count+=1 + fi + else + echo "❌ test_$task build FAILED" + failed_count+=1 + fi + done + + echo "=== Test Results Summary ===" + echo "Total tasks in list: ${#tasks[@]}" + echo "Processed: $task_count" + echo "✅ Passed: $passed_count" + echo "❌ Failed: $failed_count" + + if [ $failed_count -gt 0 ]; then + echo "❌ Some tasks failed!" + exit 1 + elif [ $task_count -eq 0 ]; then + echo "No tasks were processed (no changes)" + exit 0 + else + echo "✅ All processed tasks passed!" + exit 0 + fi \ No newline at end of file diff --git a/.github/workflows/testing_week_04.yml b/.github/workflows/testing_week_04.yml new file mode 100644 index 00000000..9f93eaed --- /dev/null +++ b/.github/workflows/testing_week_04.yml @@ -0,0 +1,95 @@ +name: Testing Tasks Week 04 + +on: + workflow_dispatch: + inputs: + tasks: + description: 'Select tasks to test' + required: true + type: choice + default: 'all' + options: + - all + - stack + - queue + - ring_buffer + - phasor + + schedule: + - cron: '59 20 26 12 *' # UTC: 20:59 = 23:59 MSK 26 December + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install compiler and CMake + run: sudo apt install -y cmake build-essential g++-14 libgtest-dev libgmock-dev + + - name: Configure project + run: cmake -B build + + - name: Determine tasks to run + id: get-tasks + run: | + if [ "${{ github.event.inputs.tasks }}" = "all" ]; then + # Find all tasks + TASKS=() + for dir in 04_week/tasks/*/; do + task=$(basename "$dir") + TASKS+=("$task") + done + echo "tasks=${TASKS[*]}" >> $GITHUB_OUTPUT + else + # Используем указанную задачу + echo "tasks=${{ github.event.inputs.tasks }}" >> $GITHUB_OUTPUT + fi + + - name: Build and run tests for selected tasks + run: | + + IFS=' ' read -ra tasks <<< "${{ steps.get-tasks.outputs.tasks }}" + + declare -i passed_count=0 + declare -i failed_count=0 + declare -i task_count=0 + + echo "=== Starting tests for selected tasks ===" + + for task in "${tasks[@]}"; do + task_count+=1 + echo "=== Processing $task ===" + + if cmake --build build --target test_$task; then + echo "✅ test_$task built successfully" + + if ./build/tasks/test_$task; then + echo "✅ test_$task PASSED" + passed_count+=1 + else + echo "❌ test_$task FAILED" + failed_count+=1 + fi + else + echo "❌ test_$task build FAILED" + failed_count+=1 + fi + done + + echo "=== Test Results Summary ===" + echo "Total tasks in list: ${#tasks[@]}" + echo "Processed: $task_count" + echo "✅ Passed: $passed_count" + echo "❌ Failed: $failed_count" + + if [ $failed_count -gt 0 ]; then + echo "❌ Some tasks failed!" + exit 1 + elif [ $task_count -eq 0 ]; then + echo "No tasks were processed (no changes)" + exit 0 + else + echo "✅ All processed tasks passed!" + exit 0 + fi \ No newline at end of file diff --git a/01_week/tasks/addition/addition.cpp b/01_week/tasks/addition/addition.cpp index 92872802..85cc3e47 100644 --- a/01_week/tasks/addition/addition.cpp +++ b/01_week/tasks/addition/addition.cpp @@ -1,7 +1,5 @@ #include -#include - int64_t Addition(int a, int b) { - throw std::runtime_error{"Not implemented"}; -} \ No newline at end of file + return (int64_t)a + b; +} diff --git a/01_week/tasks/char_changer/char_changer.cpp b/01_week/tasks/char_changer/char_changer.cpp index 3a7344d9..9975cbb7 100644 --- a/01_week/tasks/char_changer/char_changer.cpp +++ b/01_week/tasks/char_changer/char_changer.cpp @@ -1,7 +1,73 @@ #include -#include - +#include +#include +#include size_t CharChanger(char array[], size_t size, char delimiter = ' ') { - throw std::runtime_error{"Not implemented"}; -} + char prev_ch = array[0]; + int count_repeat = 1; + + std::string tmp; + + tmp.reserve(size); + + for (size_t i = 1; i < size - 1; ++i) { + if (array[i] == prev_ch) { + if (array[i] != ' ') { + ++count_repeat; + } + } + else { + if (std::isdigit(prev_ch)) { + tmp += '*'; + } + else if (std::isalpha(prev_ch)) { + tmp += std::toupper(prev_ch); + } + else if (prev_ch == ' ') { + tmp += delimiter; + } + else { + tmp += '_'; + } + + if ((count_repeat > 1) && (prev_ch != ' ')) { + if (count_repeat < 10) { + tmp += std::to_string(count_repeat); + } + else { + tmp += '0'; + } + count_repeat = 1; + } + } + + prev_ch = array[i]; + } + + if (std::isdigit(prev_ch)) { + tmp += '*'; + } + else if (std::isalpha(prev_ch)) { + tmp += std::toupper(prev_ch); + } + else if (prev_ch == ' ') { + tmp += delimiter; + } + else { + tmp += '_'; + } + + if ((count_repeat > 1) && (prev_ch != ' ')) { + if (count_repeat < 10) { + tmp += std::to_string(count_repeat); + } + else { + tmp += '0'; + } + } + + std::strcpy(array, tmp.c_str()); + + return std::strlen(array); +} \ No newline at end of file diff --git a/01_week/tasks/check_flags/check_flags.cpp b/01_week/tasks/check_flags/check_flags.cpp index 75e7c652..1d3b41f0 100644 --- a/01_week/tasks/check_flags/check_flags.cpp +++ b/01_week/tasks/check_flags/check_flags.cpp @@ -1,6 +1,6 @@ #include -#include - +#include +#include enum class CheckFlags : uint8_t { NONE = 0, @@ -14,5 +14,39 @@ enum class CheckFlags : uint8_t { }; void PrintCheckFlags(CheckFlags flags) { - throw std::runtime_error{"Not implemented"}; + uint8_t bits = static_cast(flags); + + if (bits > static_cast(CheckFlags::ALL)) { + return; + } + + if (bits == static_cast(CheckFlags::NONE)) { + std::cout << "[]"; + return; + } + + std::string output = "["; + + if (bits & static_cast(CheckFlags::TIME)) { + output += "TIME,"; + } + if (bits & static_cast(CheckFlags::DATE)) { + output += "DATE,"; + } + if (bits & static_cast(CheckFlags::USER)) { + output += "USER,"; + } + if (bits & static_cast(CheckFlags::CERT)) { + output += "CERT,"; + } + if (bits & static_cast(CheckFlags::KEYS)) { + output += "KEYS,"; + } + if (bits & static_cast(CheckFlags::DEST)) { + output += "DEST,"; + } + + output.back() = ']'; + + std::cout << output; } diff --git a/01_week/tasks/length_lit/length_lit.cpp b/01_week/tasks/length_lit/length_lit.cpp index e69de29b..b2822ce0 100644 --- a/01_week/tasks/length_lit/length_lit.cpp +++ b/01_week/tasks/length_lit/length_lit.cpp @@ -0,0 +1,47 @@ +constexpr double operator""_ft_to_in(long double ft) { + return ft * 12; +} + +constexpr double operator""_ft_to_cm(long double ft) { + return ft * 30.48; +} + +constexpr double operator""_ft_to_m(long double ft) { + return ft * 0.3048; +} + +constexpr double operator""_in_to_ft(long double in) { + return in / 12; +} + +constexpr double operator""_in_to_cm(long double in) { + return in * 2.54; +} + +constexpr double operator""_in_to_m(long double in) { + return in * 0.0254; +} + +constexpr double operator""_cm_to_ft(long double cm) { + return cm / 30.48; +} + +constexpr double operator""_cm_to_in(long double cm) { + return cm / 2.54; +} + +constexpr double operator""_cm_to_m(long double cm) { + return cm / 100; +} + +constexpr double operator""_m_to_ft(long double m) { + return m / 0.3048; +} + +constexpr double operator""_m_to_in(long double m) { + return m / 0.0254; +} + +constexpr double operator""_m_to_cm(long double m) { + return m * 100; +} diff --git a/01_week/tasks/print_bits/print_bits.cpp b/01_week/tasks/print_bits/print_bits.cpp index a48a43c1..24550d28 100644 --- a/01_week/tasks/print_bits/print_bits.cpp +++ b/01_week/tasks/print_bits/print_bits.cpp @@ -1,7 +1,29 @@ #include -#include - +#include +#include +#include void PrintBits(long long value, size_t bytes) { - throw std::runtime_error{"Not implemented"}; + if (bytes == 0 || bytes > 8) { + return; + } + + std::string bits; + + for (size_t i = 0, j = bytes * 8; i < j; i++) { + if (value & (1LL << i)) { + bits += '1'; + } + else { + bits += '0'; + } + + if (((i+1) % 4 == 0) && (i > 0) && (i < j - 1)) { + bits += '\''; + } + } + + std::reverse(bits.begin(), bits.end()); + + std::cout << "0b" << bits << '\n'; } diff --git a/01_week/tasks/quadratic/quadratic.cpp b/01_week/tasks/quadratic/quadratic.cpp index abf7d632..5dd261ba 100644 --- a/01_week/tasks/quadratic/quadratic.cpp +++ b/01_week/tasks/quadratic/quadratic.cpp @@ -1,6 +1,44 @@ -#include - +#include +#include +#include void SolveQuadratic(int a, int b, int c) { - throw std::runtime_error{"Not implemented"}; -} \ No newline at end of file + std::streamsize precision = std::cout.precision(); + std::ios_base::fmtflags flags = std::cout.flags(); + + std::cout << std::setprecision(6); + + if (a == 0) { + if (b == 0) { + if (c == 0) { + std::cout << "infinite solutions"; + } + else { + std::cout << "no solutions"; + } + } + else { + double x = (double)(-c) / b; + std::cout << x; + } + } + else { + double D = b*b - 4*a*c; + + if (D == 0) { + double x = -b / (2.0*a); + std::cout << x; + } + else if (D > 0) { + double x1 = (-b - std::sqrt(D)) / (2.0*a); + double x2 = (-b + std::sqrt(D)) / (2.0*a); + std::cout << x1 << " " << x2; + } + else { + std::cout << "no solutions"; + } + } + + std::cout.precision(precision); + std::cout.flags(flags); +} diff --git a/01_week/tasks/rms/rms.cpp b/01_week/tasks/rms/rms.cpp index 6882f0a9..d8889c57 100644 --- a/01_week/tasks/rms/rms.cpp +++ b/01_week/tasks/rms/rms.cpp @@ -1,7 +1,14 @@ -#include -#include - +#include +#include double CalculateRMS(double values[], size_t size) { - throw std::runtime_error{"Not implemented"}; -} \ No newline at end of file + if (values == nullptr || !size) + return 0.0; + + double sum = 0.0; + + for (size_t i = 0; i < size; ++i) + sum += values[i] * values[i]; + + return std::sqrt(sum / size); +} diff --git a/02_week/tasks/func_array/func_array.cpp b/02_week/tasks/func_array/func_array.cpp index b327e68d..42c7deb0 100644 --- a/02_week/tasks/func_array/func_array.cpp +++ b/02_week/tasks/func_array/func_array.cpp @@ -1,6 +1,17 @@ -#include +#include +double ApplyOperations(double a, double b, double (* const f[])(double, double), size_t length) { + double sum = 0.0; -double ApplyOperations(double a, double b /* other arguments */) { - throw std::runtime_error{"Not implemented"}; + if (!f || !length) { + return sum; + } + + for (size_t i = 0; i < length; i++) { + if (f[i]) { + sum += f[i](a, b); + } + } + + return sum; } \ No newline at end of file diff --git a/02_week/tasks/last_of_us/last_of_us.cpp b/02_week/tasks/last_of_us/last_of_us.cpp index c7bf1a25..f6724ec8 100644 --- a/02_week/tasks/last_of_us/last_of_us.cpp +++ b/02_week/tasks/last_of_us/last_of_us.cpp @@ -1,6 +1,17 @@ -#include +const int* FindLastElement(const int* begin, const int* end, bool (*predicate)(int)) { + if (!begin || !end || !predicate) { + return end; + } + if (begin > end) { + return end; + } + + for (const int *p = end - 1; p != begin - 1; p--) { + if (predicate(*p)) { + return p; + } + } -/* return_type */ FindLastElement(/* ptr_type */ begin, /* ptr_type */ end, /* func_type */ predicate) { - throw std::runtime_error{"Not implemented"}; + return end; } \ No newline at end of file diff --git a/02_week/tasks/little_big/little_big.cpp b/02_week/tasks/little_big/little_big.cpp index abe24379..847b8330 100644 --- a/02_week/tasks/little_big/little_big.cpp +++ b/02_week/tasks/little_big/little_big.cpp @@ -1,10 +1,69 @@ -#include +#include +#include +#include +void PrintMemory(int value, bool is_big_endian = false) { + char digits[8 + 1]; -void PrintMemory(int /* write arguments here */) { - throw std::runtime_error{"Not implemented"}; + std::memset(digits, '0', sizeof(digits)); + + for (size_t i = 0; i < 4; ++i) { + uint8_t byte = *(reinterpret_cast(&value) + i); + + for (size_t j = 0; j < 2; ++j) { + uint8_t code = (byte & (0b1111 << (j * 4))) >> (j * 4); + + char ch = (code < 10) ? '0' + code : 'A' + code - 10; + + if (is_big_endian) { + digits[i * 2 + j] = ch; + } + else { + digits[(3 - i) * 2 + j] = ch; + } + } + } + + digits[8] = '\0'; + + std::cout << "0x"; + + for (int i = 7; i >= 0; --i) { + std::cout << digits[i]; + } + + std::cout << '\n'; } -void PrintMemory(double /* write arguments here */) { - throw std::runtime_error{"Not implemented"}; +void PrintMemory(double value, bool is_big_endian = false) { + char digits[16 + 1]; + + std::memset(digits, '0', sizeof(digits)); + + for (size_t i = 0; i < 8; ++i) { + uint8_t byte = *(reinterpret_cast(&value) + i); + + for (size_t j = 0; j < 2; ++j) { + uint8_t code = (byte & (0b1111 << (j * 4))) >> (j * 4); + + char ch = (code < 10) ? '0' + code : 'A' + code - 10; + + if (is_big_endian) { + digits[i * 2 + j] = ch; + } + else { + digits[(7 - i) * 2 + j] = ch; + } + } + } + + digits[16] = '\0'; + + std::cout << "0x"; + + for (int i = 15; i >= 0; --i) { + std::cout << digits[i]; + } + + std::cout << '\n'; } \ No newline at end of file diff --git a/02_week/tasks/longest/longest.cpp b/02_week/tasks/longest/longest.cpp index 04b3c354..3499d659 100644 --- a/02_week/tasks/longest/longest.cpp +++ b/02_week/tasks/longest/longest.cpp @@ -1,6 +1,47 @@ -#include +#include +char* FindLongestSubsequence(const char* begin, const char* end, size_t& count) { + count = 0; + + if (!begin || !end) { + return nullptr; + } -/* return_type */ FindLongestSubsequence(/* ptr_type */ begin, /* ptr_type */ end, /* type */ count) { - throw std::runtime_error{"Not implemented"}; -} + if (begin >= end) { + return nullptr; + } + + char prev_char = '\0'; + + size_t prev_seq_len = 1; + const char* prev_seq = begin; + + size_t max_seq_len = prev_seq_len; + const char* max_seq = prev_seq; + + for (auto p = begin; p != end; ++p) { + if (*p == prev_char) { + ++prev_seq_len; + } + else { + if (prev_seq_len > max_seq_len) { + max_seq_len = prev_seq_len; + max_seq = p - prev_seq_len; + } + + prev_seq_len = 1; + prev_seq = p; + } + + prev_char = *p; + } + + if (prev_seq_len > max_seq_len) { + max_seq_len = prev_seq_len; + max_seq = end - prev_seq_len; + } + + count = max_seq_len; + + return const_cast(max_seq); +} \ No newline at end of file diff --git a/02_week/tasks/pretty_array/pretty_array.cpp b/02_week/tasks/pretty_array/pretty_array.cpp index 48eab341..5c78467d 100644 --- a/02_week/tasks/pretty_array/pretty_array.cpp +++ b/02_week/tasks/pretty_array/pretty_array.cpp @@ -1,6 +1,28 @@ -#include +#include +void PrintArray(const int* begin, const int* end, size_t limit = 0) { + if (!begin || !end) { + std::cout << "[]\n"; + return; + } -void PrintArray(/* write arguments here */) { - throw std::runtime_error{"Not implemented"}; + size_t length = (begin > end) ? (begin - end) : (end - begin); + + std::cout << '['; + + for (size_t i = 0; i < length; ++i) { + std::cout << ((begin < end) ? begin[i] : end[length - i]); + + if ((i + 1) == length) { + break; + } + + std::cout << ", "; + + if ((limit > 0) && ((i + 1) % limit == 0)) { + std::cout << "...\n "; + } + } + + std::cout << "]\n"; } \ No newline at end of file diff --git a/02_week/tasks/swap_ptr/swap_ptr.cpp b/02_week/tasks/swap_ptr/swap_ptr.cpp index 93db625d..20e56bc6 100644 --- a/02_week/tasks/swap_ptr/swap_ptr.cpp +++ b/02_week/tasks/swap_ptr/swap_ptr.cpp @@ -1,6 +1,6 @@ -#include +void SwapPtr(auto& a, auto& b) { + auto tmp = a; - -void SwapPtr(/* write arguments here */) { - throw std::runtime_error{"Not implemented"}; + a = b; + b = tmp; } \ No newline at end of file diff --git a/03_week/03_data_structures.md b/03_week/03_data_structures.md new file mode 100644 index 00000000..67aeea52 --- /dev/null +++ b/03_week/03_data_structures.md @@ -0,0 +1,916 @@ +# Лекция 3. Структуры данных. Перегрузка функций. Вектор + +1. [Структура `struct`](#struct) + - [Размер структуры](#struct_sizeof) + - [Создание объекта](#struct_var) + - [Инициализация полей значениями по умолчанию](#struct_default_value) + - [Доступ к полям структуры. Оператор `.`](#operator_dot) + - [Доступ к полям структуры. Оператор `->`](#operator_arrow) +1. [Объединение `union`](#union) + - [Использование неактивной переменной](#union_using_inactive) + - [Анонимные объединения](#union_anonymous) +1. [Пара `std::pair`](#std_pair) + - [Инициализация и доступ к полям](#std_pair_access) + - [Доступ к полям с помощью `std::get` (_C++11_)](#std_pair_get) + - [Создание с помощью `std::make_pair`](#std_make_pair) +1. [Кортеж `std::tuple` (_С++11_)](#std_tuple) + - [Инициализация](#std_tuple_init) + - [Доступ к полям с помощью `std::get`](#std_tuple_get) + - [Создание с помощью `std::make_tuple`](#std_make_tuple) +1. [Функция связывания `std::tie`](#std_tie) +1. [Структурное связывание (_structered bindings_) _C++17_](#structered_bindings) +1. [Перегрузка функций](#overloading_function) + - [Перегрузка операторов](#overloading_operator) +1. [Асимптотическая сложность](#big_o_notation) +1. [Последовательные контейнеры](#sequentual_container) +1. [Контейнер `std::vector`](#std_vector) + - [Инициализация `std::vector`](#std_vector_init) + - [Устройство `std::vector`](#std_vector_structure) + - [Обзор методов `std::vector`](#std_vector_overview) + - [Вместимость контейнера `std::vector`](#std_vector_capacity) + - [Добавление в конец `std::vector`](#std_vector_push_back) + - [Доступ к элементам `std::vector`](#std_vector_access) + - [Итераторы](#iterators) + - [Итераторы `std::vector`](#std_vector_iterators) + - [Итерирование `std::vector`](#std_vector_iterating) + - [Метод `resize()`](#std_vector_resize) + - [Метод `reserve()`](#std_vector_reserve) + - [Метод `pop_back()`](#std_vector_pop_back) + - [Метод `clear()`](#std_vector_clear) + - [Остальные методы `std::vector`](#std_vector_overview) + + +## Структура `struct` + +Структура представляет собой пользовательский тип данных, позволяющий +объединять несколько переменных разных типов под одним именем. + +Синтаксис определения: `struct { ... };` + +- `` - имя структуры, является именем нового пользовательского типа данных +- `...` - ноль и более переменных + +Синтаксис объявления: `struct ;` + +- может пригодиться, когда необходимо использовать структуру раньше, чем + она определена. + +Переменная, объявленная внутри структуры, называется **полем** структуры. + +Гарантируется, что поля структуры располагаются в памяти в порядке +объявления полей внутри структуры. + +### Размер структуры + +Размер структуры зависит от размера входящих в неё полей и выравнивания +(_alighnment_). + +```c++ +struct Point { int x; int y; }; +struct IID { int x; int y; double d; }; +struct IDI { int x; double d; int y;}; +struct IDIB { int x; double d; int y; bool b}; + +std::cout << sizeof(Point) << std::endl; // 8 +std::cout << sizeof(IID) << std::endl; // 16 +std::cout << sizeof(IDI) << std::endl; // 24 +std::cout << sizeof(IDIB) << std::endl; // 24 +``` + +### Создание объекта + +Имя структуры используется в качестве имени типа при создании объекта. + +Синтаксис: ` ;` + +Рекомендуется сразу инициализировать поля объекта, сделать это можно, +используя фигурные скобки `{}` и перечисляя значение полей в том порядке, +в каком они определены в структуре. Также, можно использовать `()` +непосредственно после имени объекта. + +```c++ +struct Product { + int count; + double price; +}; + +Product obj; +Product obj_empty = {}; +Product obj_init = {10, 300.0}; +Product obj_part = {10}; // partitial +Product obj_agr{10, 300.0}; // uniform +``` + +Имя типа структуры может совпадать с каким-либо объектом, тогда при объявлении +можно разрешить конфликт имен добавив `struct` перед именем типа структуры +(как правило, лучше **ИЗБЕЖАТЬ** совпадающих названий): + +Синтаксис: `struct ;` + +```c++ +struct Product { + int count; + double price; +}; + +std::string Product = "some_product_name"; +// Product p = {10, 300.0}; // compile error +struct Product p = {10, 300.0}; // OK +``` + +Можно объявить структуру сразу с созданием объектов данного типа, **НО** +лучше так **НЕ** делать, особенно в глобальной области видимости. + +Синтаксис: `struct { ... } , ...;` + +```c++ +struct Point { + int x; + int y; +} p1, p2; +``` + +### Инициализация полей значениями по умолчанию + +Начиная с _C++11_ можно инициализировать поля значениями по умолчанию +через `=` при определении структуры. Тогда при создании объекта данного +типа поля будут инициализированы значениями по умолчанию. + +```c++ +struct Product { + int count = 0; + double price = 100.0; +}; + +Product one; +Product two = {}; +Product three{}; +``` + +### Доступ к полям структуры. Оператор `.` + +Доступ к полям структуры через объект типа структуры осуществляется через +оператор `.` точка. После имени объекта ставится `.` и затем имя поля: + +```c++ +struct Product { + int count; + double price; +}; + +Product obj; +obj.count = 10; +obj.price = 300.0; +``` + +### Доступ к полям структуры. Оператор `->` + +Доступ к полям структуры через указатель на объект типа структуры +осуществляется через оператор `->` стрелочка. После указателя на объект +ставится `->` и затем имя поля: + +```c++ +struct Product { + int count; + double price; +}; + +Product obj; +Product* ptr = &obj; +ptr->count = 10; +ptr->price = 300.0; +``` + +## Объединение `union` + +Объединение представляет собой пользовательский тип данных, который позволяет +хранить разные типы данных в одной области памяти. В один момент может храниться +только один тип данных. Размер объединения соответствует наибольшему по размеру +типу, входящего в объединение. + +Синтаксис: `union { ... };` + +```c++ +union Union { + char c; + int i; + double d; +}; +``` + +Все входящие в объединение типы переменных расположены по одному адресу. + +Доступ к переменным аналогичен структурам. + +### Использование неактивной переменной + +Активная переменная определяется последним присваиванием, при этом +время жизни предыдущей активной переменной заканчивается. Использование +неактивной переменной это **UB**. Однако, современные компиляторы, как +правило, представляют расширение языка и позволяют читать неактивные +переменные. Таким образом, можно получить доступ к отдельным байтам. + +```c++ +union Color { + uint32_t value; + struct { + uint8_t a + uint8_t b; + uint8_t g; + uint8_t r; + } components; +}; + +Color c; +c.value = 0x00FF9955; + +std::cout << "Red: " << c.components.r << std::endl; // 0 +std::cout << "Green: " << c.components.g << std::endl; // +std::cout << "Blue: " << c.components.b << std::endl; // 255 +std::cout << "Alpha: " << c.components.a << std::endl; // 0 +``` +- но это по-прежнему **UB** и платформо-зависимый код, на _little-endian_ + и _big-endian_ различный вывод. + +Аналогично можно работать отдельно с битами: + +```c++ +union HardwareRegister { + uint32_t raw; + struct { + uint32_t enable : 1; + uint32_t mode : 3; + uint32_t clock_divider : 4; + uint32_t reserved : 24; + } bits; // 1 + 3 + 4 + 24 = 32 +}; + +HardwareRegister reg; +reg.raw = 0; +reg.bits.enable = 1; +reg.bits.mode = 5; +reg.bits.clock_divider = 8; +``` + +### Анонимные объединения + +Можно создавать анонимные объединения, тогда все внутренние имена переменных +попадают во внешнюю область видимости объединения и не должно быть +конфликта имен. + +```c++ +struct Variant { + enum Type { INT, FLOAT, STRING } type; + + union { + int intValue; + float floatValue; + char stringValue[50]; + }; +}; +``` + +## Пара `std::pair` + +Пара `std::pair` - это шаблонная структура, позволяющая хранить переменные +двух типов в полях `first` и `second`. + +Заголовочный файл: `` + +Объявление: `std::pair ;` +- `T1` - тип первой переменной +- `T2` - тип второй переменной + +Методы: +- `swap` - для обмена значений с другой парой + +Доступны операторы сравнения, сравнение производится лексикографически. + +Пара используется в стандартных ассоциативных контейнерах, при возврате +ключа и значения контейнера. А также, может быть использована при возврате +двух значений из функции. Как правило, это менее информативно, чем структура +с понятными именами полей. + +### Инициализация и доступ к полям + +Доступ к соответствующим переменным в паре, осуществляется через имена полей +`first` и `second`. + +Инициализировать пару: + +```c++ +std::pair p; // uninitialized +p.first = 'c'; +p.second = static_cast(p.first); + +std::pair p1 = {true, "Book"}; +std::pair p2(10, 3.14); // ctor +std::pair p3{true, 2.73}; // uniform C++11 +``` + +### Доступ к полям с помощью `std::get` (_C++11_) + +В заголовочном файле `` есть шаблонная функция `std::get`, которая +на вход принимает пару и возвращает ссылку на поле пары. + +Может работать по индексу: +- `0` - `first` +- `1` - `second` +- другие значения приведут к ошибке компиляции + +Может работать по типу переменной, в случае одинаковых типов будет ошибка +компиляции. + +```c++ +std::pair p = {true, 10}; +std::cout << '(' << std::get<0>(p) << ", " << std::get<1>(p) << ')' << std::endl; +std::cout << '(' << std::get(p) << ", " << std::get(p) << ')' << std::endl; +std::get(p) = false; +std::get<1>(p) = 0; +``` + +### Создание с помощью `std::make_pair` + +В заголовочном файле `` есть шаблонная функция `std::make_pair` +принимающая два аргумента и возвращающая пару из двух аргументов. +Удобство заключается в возможности использования вывода типа переменной, +если написать ключевое слово `auto`. + +```c++ +auto p1 = std::make_pair(15, 'c'); // std::pair +auto p2 = std::make_pair(true, 3.14); // std::pair + +int value = 3; +int array[] = {1, 2, 3}; +auto p3 = std::make_pair(std::ref(value), array); // std::pair +``` + +## Кортеж `std::tuple` (_С++11_) + +Кортеж `std::tuple` - шаблонный класс, позволяющий хранить произвольное число +аргументов различных типов. + +Заголовочный файл: `` + +Объявление: `std::tuple ;` +- `T...` - произвольное количество различных типов + +Методы: +- `swap` - для обмена значений с другим аналогичным по типам кортежем + +Для кортежа определены операторы сравнения. + +### Инициализация + +Инициализировать кортеж можно привычными способами: + +```c++ +std::tuple t; // uninitialized +std::tuple t1 = {true, "Book", 10}; // copy +std::tuple t2(10, 3.14, -1); // ctor +std::tuple p3{true, 2.73, 0, '0'}; // uniform C++11 +``` + +### Доступ к полям с помощью `std::get` + +Поскольку кортеж не имеет прямого доступа к полям, то получить значения +полей кортежа или изменить их можно с помощью шаблонной функции `std::get`. + +Функция принимает кортеж, а в качестве параметров шаблона внутри `<>` +указывается либо порядковый номер начиная с `0` и меньше количества переменных, +либо тип данных, если он уникален для кортежа + +```c++ +std::tuple tpl(10., 'a', 10); +std::get<0>(tpl) += 1; +std::get<1>(tpl) += 1; +std::get<2>(tpl) += 1; +// tpl = {11, 'b', 11}; +std::get(tpl) += 1; +std::get(tpl) += 1; +std::get(tpl) += 1; +// tpl = {12, 'c', 12}; +``` + +### Создание с помощью `std::make_tuple` + +В заголовочном файле `` есть шаблонная функция `std::make_tuple` +принимающая произвольное число аргументов различных типов и возвращающая +кортеж из переданных аргументов. + +Удобство заключается в возможности использования вывода типа переменной, +если написать ключевое слово `auto`. + +```c++ +auto t1 = std::make_tuple(15, 'c', 3.14); // std::tuple + +struct Product { int count; double price; }; +auto t2 = std::make_pair(true, Product{10, 3.14}); // std::tuple +std::cout << std::get<1>(t2).count << " " << std::get<1>(t2).price; +``` + +## Функция связывания `std::tie` + +Шаблонная функция позволяющая создать кортеж ссылок на переданные +аргументы или ссылку на специальный объект `std::ignore` + +- применяется для лексикографического сравнения структур: + +```c++ +struct Pair { int value; std::string name; } p1, p2; +std::tie(p1.value, p1.name) < std::tie(p2.value, p2.name); +``` + +- для распаковки кортежа или пары (так как пара конвертируется в кортеж): + +```c++ +int i; +double d; +std::tie(i, std::ignore, d) = std::tuple(1, "Test", 2.0); +``` + +## Структурное связывание (_structered bindings_) _C++17_ + +_Structered bindings_ - синтаксическая конструкция, введенная в _C++17_, +которая позволяет распаковывать составляющие составных типов данных +в отдельные переменные. + +Синтаксис: ` auto [...] = ;` + +Работает с массивами, структурами, парами, кортежами. + +```c++ +int array[] = {1, 2}; + +auto& [first, second] = array; +first = 3; +second = 4; // array = {3, 4}; +``` + +```c++ +struct Product { int count; double price; }; + +Product prod{10, 100.0}; +auto [count, price] = prod; +auto& [ref_count, ref_price] = prod; +const auto& [cref_count, cref_price] = prod; +``` + +```c++ +std::pair p(true, 10); +const auto& [b, i] = p; +``` + +```c++ +std::tuple tpl(10., 'a', 10); +auto [f, c, i] = tpl; +``` + +## Перегрузка функций + +Перегрузка функций позволяет иметь функции с одинаковыми именами для различных +входных аргументов. + +Перегрузить функцию можно только по аргументам, изменение возвращаемого типа, +без изменения аргументов **НЕ** является перегрузкой. + +CV-квалификаторы изменяют тип, поэтому добавление константности к аргументам +является перегрузкой. + +```c++ +void PrintValues(const int* begin, const int* end) {} +void PrintValues(int* begin, int* end) {} +void PrintValues(const int* array, size_t size) {} +void PrintValues(const Point& point) {} +// Point PrintValues(const Point& point) {} // compile error +``` + +Функция, имеющая значения по умолчанию, имеет перегрузки функции, без +аргументов по умолчанию, причем количество перегрузок соответствует +количеству аргументов по умолчанию. + +```c++ +void PrintValues(int x, float f = 0.0, char c = 'a') { + std::cout << "x = " << x << ", f = " << f << ", c = " << c << std::endl; +} + +PrintValues(x, f, c); +PrintValues(x, f); +PrintValues(x); +``` + +### Перегрузка операторов + +Поскольку операторы являются функциями, то операторы можно перегрузить. +Что позволяет использовать операторы для своих типов данных. + +Синтаксис: ` operator(...);` + +- `` - символьное обозначение оператора (`+`, `<<`, `==`, ...) +- `...` - один или два аргумента (для унарного и бинарного операторов) +- `` - тип возвращаемого значения + +**НЕЛЬЗЯ** перегрузить операторы: `::`, `?:`, `.`, `.*`, `typeid`, `sizeof`, `alignof` + +Перегрузка оператора `<<` для вывода в поток: + +```c++ +struct Pair { + int value; + std::string name; +}; + +std::ostream& operator<<(std::ostream& os, const Pair& pair) { + os << '(' << value << ", " << name << ')'; + return os; +} +``` + +Перегрузка операторов сравнения: + +```c++ +bool operator==(const Pair& lhs, const Pair& rhs) { + return lhs.value == rhs.value && lhs.name == rhs.name; +} + +bool operator<(const Pair& lhs, const Pair& rhs) { + if (lhs.value == rhs.value) { + return lhs.name < rhs.name; + } + return lhs.value < rhs.value; +// return (lhs.value == rhs.value) ? lhs.name < rhs.name : lhs.value < rhs.value; +// return std::tie(lhs.value, lhs.name) < std::tie(rhs.value, rhs.name); +} + +bool operator!=(const Pair& lhs, const Pair& rhs) { + return !(lhs == rhs); +} + +bool operator>(const Pair& lhs, const Pair& rhs) { + return !(lhs < rhs || lhs == rhs); +} + +bool operator>=(const Pair& lhs, const Pair& rhs) { + return !(lhs < rhs); +} + +bool operator<=(const Pair& lhs, const Pair& rhs) { + return lhs < rhs || lhs == rhs; +} +``` + +## Асимптотическая сложность + +Асимптотическая сложность — это математический способ оценки, как время +выполнения или объём потребляемой памяти алгоритма растут с увеличением +размера входных данных (`n`), когда `n` стремится к бесконечности. + +При оценке игнорируются константы и младшие члены в формуле, что позволяет +сосредоточиться на основном факторе масштабирования. Оценка позволяет +сравнивать эффективность алгоритмов независимо от аппаратного обеспечения. + +Обычно используется O-нотация («О большое») для определения верхней границы +(наихудшего случая). + +Распространенная асимптотическая сложность: + +1. $O(1)$ — Константная сложность + - Время выполнения не зависит от размера входных данных. + +1. $O(log(n))$ — Логарифмическая сложность + - Медленный рост. Удвоение `n` увеличивает время работы лишь на константу. + +1. $O(n)$ — Линейная сложность + - Время растет прямо пропорционально n. + +1. $O(n \cdot log(n))$ — Линейно-логарифмическая сложность + - Золотой стандарт для эффективных алгоритмов сортировки общего назначения. + +1. $O(n^a)$ — Степенная сложность + - Время растет пропорционально степени размера данных. При больших `n` + становится очень медленно. + +1. $O(2^n)$ — Экспоненциальная сложность + - Чрезвычайно быстрый рост. Практически неприменимо для задач с `n` > 30-40. + +1. $O(n!)$ — Факториальная сложность + - Рост быстрее экспоненциальной. Характерна для задач полного перебора всех перестановок. + +## Последовательные контейнеры v + +Стандартная библиотека _STL_ содержит множество шаблонных классов, которые +представляют различные структуры данных. Как правило, их называют контейнерами. +Контейнеры способны хранить произвольный тип данных. Контейнеры представляют +собой коллекцию элементов определенного типа. Контейнеры предоставляют +удобный способ взаимодействовать с элементами + +В качестве параметров шаблона внутри треугольных скобок `<>` обычно необходимо +указать тип данных, который использует контейнер. + +Для последовательных контейнеров определен оператор доступа по индексу `[]` + +## Контейнер `std::vector` + +Контейнер `std::vector` предназначен для хранения массива данных определенного типа +непрерывно в динамической памяти. + +Контейнер обеспечивает быстрое добавления элементов в конец и увеличение размера +блока памяти под элементы при необходимости. + +Для гарантии непрерывности памяти, при добавлении элемента в заполненный контейнер, +происходит **реалокация** всех элементов контейнера. + +Заголовочный файл: `` + +Синтаксис: `std::vector ;` +- `T` - произвольный тип данных +- `` - имя переменной + +### Инициализация `std::vector` + +Доступны множество способов инициализации вектора: + +```c++ +std::vector v1; // empty +std::vector v2(v1); // copy ctor from other vector +std::vector v3 = v1; // copy ctor from other vector +// ctor +std::vector v4(5); // 5 elemets with zero value +std::vector v5(5, 2); // 5 elements equal 2 +// initializer list +std::vector v6{1, 2, 4, 5}; // with elements 1, 2, 4, 5 +std::vector v7 = {1, 2, 3, 5}; // with elements 1, 2, 3, 5 +// iterators +std::vector v8(v7.begin(), v7.end()); // same as v7 +``` + +### Устройство `std::vector` + +Контейнер должен иметь **указатель на данные** (адрес начала памяти в куче, +выделенной под хранение элементов вектора), **вместимость выделенной памяти** +(сколько элементов данного типа можно положить), **текущее количество элементов** +(сколько элементов данного типа уже положено в контейнер). + +Условно можно представить себе, что хранится 3 поля: указатель на данные, +размер данных _size_, размер выделенной памяти _capacity_ + +Контейнер имеет размер 24 байта. + +Фактически, стандартный контейнер устроен несколько иначе, внутри хранится +3 указателя: на начало данных, за конец выделенной памяти, за конец элементов + +Элементы контейнера располагаются не на **стеке**, а в области памяти, называемой +куча. + +### Обзор методов `std::vector` + +Контейнер имеет встроенные функции для работы с контейнером, такие функции +называются **методы**. + +Доступ к элементам: +- `at(size_t pos)` - метод, принимающий индекс элемента и возвращающий ссылку +- `[size_t pos]` - оператор `[]`, принимающий индекс и возвращающий ссылку +- `font()` - метод, возвращающий ссылку на первый элемент +- `back()` - метод, возвращающий ссылку на последний элемент +- `data()` - метод, возвращающий указатель на начало данных + +Вместимость контейнера: +- `empty()` - возвращает `true` для пустого контейнера +- `size()` - возвращает количество элементов +- `capacity()` - возвращает вместимость контейнера +- `reserve(size_t count)` - выделяет память под `count` элементов +- `shrink_to_fit()` - уменьшает вместимость вектора в соответствии с `size()` + +Модифицирующие методы: +- `assign()` - заменить текущее содержимое на новое +- `clear()` - очистить содержимое (без фактического очищения) +- `push_back()` - добавить элемент в конец контейнера +- `pop_back()` - удалить последний элемент из контейнера +- `insert()` - добавить элементы в определенную позицию +- `erase()` - удалить элементы с определенной позиции +- `resize()` - изменить количество элементов контейнера +- `swap()` - обменять содержимое контейнеров. + +### Вместимость контейнера `std::vector` + +По умолчанию, неинициализированный, пустой контейнер **НЕ** выделяет память +под элементы. + +Инициализированный контейнер выделяет память ровно под количество элементов. + +Методы, проверяющие вместимость и размер: + +- `empty()` — возвращает true, если контейнер пуст +- `size()` — возвращает текущий размер, количество элементов в контейнере +- `capacity()` — возвращает вместимость контейнера, под какое количество + элементов выделена память + +### Добавление в конец `std::vector` + +Контейнер оптимизирован под добавление элементов в конец. + +Метод, добавляющий элемент в конец: + +- `push_back()` - добавить элемент, принимаемый в аргументах, в конец контейнера + +Заполнение контейнера в цикле (не оптимально). Несколько раз в ходе заполнения +контейнера произойдет реалокация, то есть выделится новый блок памяти в куче, +достаточный для элементов контейнера, и все элементы будут скопированы по +новому адресу. + +Как правило, при добавлении элемента в полностью заполненный контейнер, +вместимость контейнера будет **увеличиваться в 2 раза** + +Сложность: **амортизированная O(1)** + +```c++ +std::vector v; +size_t capacity = v.capacity(); + +std::cout << v.data() << std::endl; // int* (address) +std::cout << v.size() << ' ' << v.capacity() << std::endl; +for (int i = 0; i < 100; ++i) { + v.push_back(i); + if (capacity < v.capacity()) { + capacity = v.capacity(); + + std::cout << v.data() << std::endl; // int* (address) + std::cout << v.size() << ' ' << v.capacity() << std::endl; + } +} +``` + +### Доступ к элементам `std::vector` + +Доступ следует осуществлять только к валидным элементам. + +Доступ к элементам осуществляется с помощью оператора `[]`, принимающего +индекс, и с помощью методов: + +- `[size_t pos]` — принимает индекс и возвращает ссылку на элемент +- `at(size_t pos)` — принимает индекс и возвращает ссылку на элемент, если + переданный индекс (позиция) невалиден, то выбрасывается исключение + `std::out_of_range` +- `fornt()` — возвращает ссылку на первый элемент +- `back()` — возвращает ссылку на последний элемент +- `data()` — возвращает сырой указатель на начало данных + +Доступ к элементам вне диапазона контейнера, получение ссылок на элементы +для пустого контейнера (`v.empty()` возвращает `true`) это **UB** + +### Итераторы + +Итераторы - фундаментальная концепция языка C++, выступающая в качестве +обобщения указателей. Итераторы обеспечивают удобный **способ доступа** и +**перемещения** по элементам различных контейнеров. + +На данном этапе будем относиться к итераторам, как к удобной обертке над +указателем. + +Итераторы имитируют поведение **указателей**: разыменование, арифметику, сравнение + +Существуют разные категории итераторов, поэтому в зависимости от контейнера +поддерживаются не все операции с итераторами + +Каждый контейнер имеет **свой тип итератора**. + +Например, для вектора целочисленных значений тип итератора: +`std::vector::iterator` + +Константная версия итератора имеет тип: +`std::vector::const_iterator` + +Используются для реализации **range-based for** и в алгоритмах стандартной +библиотеки + +Как правило, можно получить итератор на начало (первый элемент) **begin** +и на конец **end** (за последний элемент). А также, их константные версии +**cbegin** и **cend** + +### Итераторы `std::vector` + +Итератор можно получить воспользовавшись методами контейнера: + +- `begin()` — возвращает итератор на начало +- `end()` — возвращает итератор на конец +- `rbegin()` — возвращает обратный итератор на начало +- `rend()` — возвращает обратный итератор на конец + +Также присутствуют методы с приставкой c возвращающие их константные версии. + +Чтобы не писать длинный тип используется ключевое слово **auto** + +### Итерирование `std::vector` + +Контейнер поддерживает различные способы итерирования, **range-based for**: + +```c++ +std::vector v = {1, 2, 3, 5}; +for (auto vi : v) { + std::cout << vi << std::endl; +} +``` + +Итерирование по индексу (порядковыому номеру): + +```c++ +std::vector v = {1, 2, 3, 5}; +for (size_t i = 0; i < v.size(); ++i) { + std::cout << v[i] << std::endl; +} +``` + +Использование прямых итераторов: + +```c++ +std::vector v = {1, 2, 3, 5}; +for (auto it = v.begin(); it != v.end(); ++it) { + std::cout << *it << std::endl; +} +``` + +Использование обратных итераторов, для обхода контейнера в обратном порядке: + +```c++ +std::vector v = {1, 2, 3, 5}; +for (auto it = v.rbegin(); it != v.rend(); ++it) { + std::cout << *it << std::endl; +} +``` + +### Метод `resize()` + +Метод `resize()` изменяет размер контейнера, причем в случае увеличения создаются +значения по умолчанию, в случае уменьшения вместимость контейнера, как правило, +**НЕ** уменьшается + +- `resize(size_t count)` — принимает требуемое количество элементов +- `resize(size_t count, const T& value)` — принимает требуемое количество элементов + и значение элемента, которым необходимо заполнить контейнер в случае увеличения + размера + +Сложность: **O(n)** или **O(1)** при уменьшении. + +Тратятся ресурсы на заполнение **значениями по умолчанию** + +Изменяет _size_ контейнера, а также _capacity_ при необходимости **увеличения** + +### Метод `reserve()` + +Метод `reserve()` выделяет нужное количество памяти, не заполняя элементы значением +по умолчанию, при этом _size_ контейнера не изменяется, а _capacity_ может только +увеличиваться в соответствии c заданным значением + +- `reserve(size_t new_capacity)` — принимает новое значение вместимости контейнера + +Сложность: **O(n)** + +Если известна требуемая вместимость заранее, наиболее оптимальный способ создания: + +```c++ +std::vector v; +v.reserve(100); +for (size_t i = 0; i < 100; ++i) { + v.push_back(i); +} +``` + +### Метод `pop_back()` + +Метод, удаляющий последний элемент: + +- `pop_back()` — удаляет элемент из конца контейнера + +Как правило, просто уменьшает размер контейнера, или перемещает указатель на конец +элементов, **НЕ** вызывает реалокацию памяти + +Сложность: **O(1)** + +`pop_back()` от пустого контейнера (`v.empty()` возвращает `true`) это **UB** + +### Метод `clear()` + +Метод `clear()` очищает контейнер, _size_ = 0, но _capacity_ **НЕ** изменяется + +Как правило, просто уменьшает размер контейнера, перемещает указатель на конец +элементов на начало. **НЕ** освобождает память + +Сложность: **O(1)** как правило просто переносится указатель конца элементов на начало + +### Остальные методы `std::vector` + +Основные модифицирующие методы контейнера: + +- `shrink_to_fit()` — уменьшает _capacity_ в соответствии с текущим _size_, + реалокация, **O(n)** +- `insert()` — вставляет элемент или диапазон в произвольное место в контейнер, **O(n)**, + вставка в конец **O(1)**. Возвращает итератор на начало вставки. +- `erase()` — удаляет элемент или диапазон из произвольного места в контейнер, **O(n)**, + поскольку необходимо передвинуть все элементы контейнера, удалить из конца **O(1)**. + Возвращает итератор после последнего удаленного элемента. +- `swap()` — обменивает содержимое двух контейнеров, **O(1)** поскольку осуществляется + обмен указателей +- `assign()` — заменяет содержимое контейнера на указанное значение или на диапазон, + **O(n)** + +[и другие ...](https://en.cppreference.com/w/cpp/container/vector.html) \ No newline at end of file diff --git a/03_week/03_data_structures.pdf b/03_week/03_data_structures.pdf new file mode 100644 index 00000000..7b5b6359 Binary files /dev/null and b/03_week/03_data_structures.pdf differ diff --git a/03_week/CMakeLists.txt b/03_week/CMakeLists.txt new file mode 100644 index 00000000..e5df5bac --- /dev/null +++ b/03_week/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.14) # Минимальная требуемая версия CMake +project(03_week) # Необходим для инициализации cmake + +set(CMAKE_CXX_STANDARD 20) # Версия стандарта C++ +set(CMAKE_CXX_STANDARD_REQUIRED ON) # Гарантирует использование указанной версии стандарта + +set(EXAMPLES_DIR examples) # Определим переменную с именем директории +set(TASKS_DIR tasks) + +add_subdirectory(tasks) + +# Создать исполняемый файл для каждого примера +if (BUILD_EXAMPLES_03_WEEK) + add_example(struct_examples ${EXAMPLES_DIR}/struct_examples.cpp) + add_example(union_examples ${EXAMPLES_DIR}/union_examples.cpp) + add_example(vector_examples ${EXAMPLES_DIR}/vector_examples.cpp) +endif() \ No newline at end of file diff --git a/03_week/examples/struct_examples.cpp b/03_week/examples/struct_examples.cpp new file mode 100644 index 00000000..11484025 --- /dev/null +++ b/03_week/examples/struct_examples.cpp @@ -0,0 +1,172 @@ +#include +#include + +struct BoolBool { bool x; bool y; }; +struct Point { int x; int y; }; +struct IID { int x; int y; double d; }; +struct IDI { int x; double d; int y;}; +struct IDIB { int x; double d; int y; bool b;}; +struct IIDB { int x; int y; double d; bool b;}; +struct IISDB { int x; int y; std::string s; double d; bool b;}; + + +void struct_sizeof() { + std::cout << __func__ << ':' << std::endl; + std::cout << "sizeof(BoolBool): " << sizeof(BoolBool) << std::endl; // 2 + std::cout << "sizeof(Point): " << sizeof(Point) << std::endl; // 8 + std::cout << "sizeof(IID): " << sizeof(IID) << std::endl; // 16 + std::cout << "sizeof(IDI): " << sizeof(IDI) << std::endl; // 24 + std::cout << "sizeof(IDIB): " << sizeof(IDIB) << std::endl; // 24 + std::cout << "sizeof(IIDB): " << sizeof(IIDB) << std::endl; // 24 + std::cout << "sizeof(IISDB): " << sizeof(IISDB) << std::endl; // 56 + std::cout << std::endl; +} + +void struct_alignof() { + std::cout << __func__ << ':' << std::endl; + std::cout << "alignof(BoolBool): " << alignof(BoolBool) << std::endl; // 1 + std::cout << "alignof(Point): " << alignof(Point) << std::endl; // 4 + std::cout << "alignof(IID): " << alignof(IID) << std::endl; // 8 + std::cout << "alignof(IDI): " << alignof(IDI) << std::endl; // 8 + std::cout << "alignof(IDIB): " << alignof(IDIB) << std::endl; // 8 + std::cout << "alignof(IIDB): " << alignof(IIDB) << std::endl; // 8 + std::cout << "alignof(IISDB): " << alignof(IISDB) << std::endl; // 8 + std::cout << std::endl; +} + +void struct_address() { + std::cout << __func__ << ':' << std::endl; + { + BoolBool obj; + std::cout << "struct BoolBool: " << std::endl; + std::cout << "&obj: " << &obj << std::endl; + std::cout << "&obj.x: " << &(obj.x) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.x) << std::endl; + std::cout << "&obj.y: " << &(obj.y) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.y) << std::endl; + } + { + Point obj; + std::cout << "struct Point: " << std::endl; + std::cout << "&obj: " << &obj << std::endl; + std::cout << "&obj.x: " << &(obj.x) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.x) << std::endl; + std::cout << "&obj.y: " << &(obj.y) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.y) << std::endl; + } + { + IID obj; + std::cout << "struct IID: " << std::endl; + std::cout << "&obj: " << &obj << std::endl; + std::cout << "&obj.x: " << &(obj.x) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.x) << std::endl; + std::cout << "&obj.y: " << &(obj.y) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.y) << std::endl; + std::cout << "&obj.d: " << &(obj.d) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.d) << std::endl; + + } + { + IDI obj; + std::cout << "struct IDI: " << std::endl; + std::cout << "&obj: " << &obj << std::endl; + std::cout << "&obj.x: " << &(obj.x) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.x) << std::endl; + std::cout << "&obj.d: " << &(obj.d) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.d) << std::endl; + std::cout << "&obj.y: " << &(obj.y) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.y) << std::endl; + } + { + IDIB obj; + std::cout << "struct IDIB: " << std::endl; + std::cout << "&obj: " << &obj << std::endl; + std::cout << "&obj.x: " << &(obj.x) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.x) << std::endl; + std::cout << "&obj.d: " << &(obj.d) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.d) << std::endl; + std::cout << "&obj.y: " << &(obj.y) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.y) << std::endl; + std::cout << "&obj.b: " << &(obj.b) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.b) << std::endl; + } + { + IIDB obj; + std::cout << "struct IIDB: " << std::endl; + std::cout << "&obj: " << &obj << std::endl; + std::cout << "&obj.x: " << &(obj.x) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.x) << std::endl; + std::cout << "&obj.y: " << &(obj.y) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.y) << std::endl; + std::cout << "&obj.d: " << &(obj.d) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.d) << std::endl; + std::cout << "&obj.b: " << &(obj.b) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.b) << std::endl; + } + { + IISDB obj; + std::cout << "struct IISDB: " << std::endl; + std::cout << "&obj: " << &obj << std::endl; + std::cout << "&obj.x: " << &(obj.x) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.x) << std::endl; + std::cout << "&obj.y: " << &(obj.y) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.y) << std::endl; + std::cout << "&obj.s: " << &(obj.s) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.s) << std::endl; + std::cout << "&obj.d: " << &(obj.d) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.d) << std::endl; + std::cout << "&obj.b: " << &(obj.b) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.b) << std::endl; + } + std::cout << std::endl; +} + +void struct_creation() { + std::cout << __func__ << ':' << std::endl; + { + Point p; + Point p2 = {1, 2}; + Point p3{2, 3}; + Point p4 = {}; + Point p5 = {1}; + } + { + std::string Point = "some_product_name"; + // Point p = {10, 300}; // compile error + struct Point p = {10, 300}; // OK + } + std::cout << std::endl; +} + +struct Inner { + bool b = false; +}; + +struct A { + int a = 0; + Inner inner = {}; +}; + +void struct_inner() { + std::cout << __func__ << ':' << std::endl; + std::cout << "sizeof(Inner): " << sizeof(Inner) << std::endl; // 1 + std::cout << "sizeof(A): " << sizeof(A) << std::endl; // 8 + std::cout << "alignof(Inner): " << alignof(Inner) << std::endl; // 1 + std::cout << "alignof(A): " << alignof(A) << std::endl; // 4 + A obj; + std::cout << "obj.inner.b = " << obj.inner.b << std::endl; + obj.inner.b = true; + std::cout << "obj.inner.b = true" << std::endl; + std::cout << "obj.inner.b = " << obj.inner.b << std::endl; + std::cout << std::endl; +} + +int main() { + struct_sizeof(); + struct_alignof(); + struct_address(); + struct_creation(); + struct_inner(); +} + + diff --git a/03_week/examples/union_examples.cpp b/03_week/examples/union_examples.cpp new file mode 100644 index 00000000..af332dca --- /dev/null +++ b/03_week/examples/union_examples.cpp @@ -0,0 +1,117 @@ +#include +#include +#include + +union ID { + int i; + double d; +}; + +union IBC { + int i; + bool b; + char c; +}; + +union IDBC { + int i; + double d; + bool b; + char c; +}; + +void union_sizeof() { + std::cout << __func__ << ':' << std::endl; + std::cout << "sizeof(ID): " << sizeof(ID) << std::endl; // 8 + std::cout << "sizeof(IBC): " << sizeof(IBC) << std::endl; // 4 + std::cout << "sizeof(IDBC): " << sizeof(IDBC) << std::endl; // 8 + std::cout << std::endl; +} + +void union_alignof() { + std::cout << __func__ << ':' << std::endl; + std::cout << "alignof(ID): " << alignof(ID) << std::endl; // 8 + std::cout << "alignof(IBC): " << alignof(IBC) << std::endl; // 4 + std::cout << "alignof(IDBC): " << alignof(IDBC) << std::endl; // 8 + std::cout << std::endl; +} + +void union_address() { + std::cout << __func__ << ':' << std::endl; + { + ID obj; + obj.i = 42; + std::cout << "union ID: " << std::endl; + std::cout << "&obj: " << &obj << std::endl; + std::cout << "&obj.i: " << &(obj.i) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.i) << std::endl; + obj.d = 3.14; + std::cout << "&obj.d: " << &(obj.d) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.d) << std::endl; + } + { + IBC obj; + obj.i = 42; + std::cout << "union IBC: " << std::endl; + std::cout << "&obj: " << &obj << std::endl; + std::cout << "&obj.i: " << &(obj.i) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.i) << std::endl; + obj.b = true; + std::cout << "&obj.b: " << &(obj.b) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&(obj.b)) << std::endl; + obj.c = 'c'; + std::cout << "&obj.c: " << static_cast(&(obj.c)) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&(obj.c)) << std::endl; + } + { + IDBC obj; + obj.i = 42; + std::cout << "union IDBC: " << std::endl; + std::cout << "&obj: " << &obj << std::endl; + std::cout << "&obj.i: " << &(obj.i) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.i) << std::endl; + obj.d = 3.14; + std::cout << "&obj.d: " << &(obj.d) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.d) << std::endl; + obj.b = true; + std::cout << "&obj.b: " << &(obj.b) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.b) << std::endl; + obj.c = 'c'; + std::cout << "&obj.c: " << reinterpret_cast(std::addressof(obj.c)) << " distance =" + << reinterpret_cast(&obj) - reinterpret_cast(&obj.c) << std::endl; + } + std::cout << std::endl; +} + +union Color { + uint32_t value; + struct { + uint8_t a; + uint8_t b; + uint8_t g; + uint8_t r; + } components; +}; + +void union_read_inactive() { + std::cout << __func__ << ':' << std::endl; + Color c; + c.value = 0x00FF9955; + + std::cout << "Red: " << +c.components.r << std::endl; // 0 + std::cout << "Green: " << +c.components.g << std::endl; // 255 + std::cout << "Blue: " << +c.components.b << std::endl; // 153 + std::cout << "Alpha: " << +c.components.a << std::endl; // 85 + std::cout << std::endl; +} + + + +int main() { + union_sizeof(); + union_alignof(); + union_address(); + union_read_inactive(); +} + + diff --git a/03_week/examples/vector_examples.cpp b/03_week/examples/vector_examples.cpp new file mode 100644 index 00000000..42afa72f --- /dev/null +++ b/03_week/examples/vector_examples.cpp @@ -0,0 +1,89 @@ +#include +#include + +void vector_init() { + std::cout << __func__ << ':' << std::endl; + std::vector v1; // empty + std::vector v2(v1); // copy ctor from other vector + std::vector v3 = v1; // copy ctor from other vector + // ctor + std::vector v4(5); // 5 elemets with zero value + std::vector v5(5, 2); // 5 elements equal 2 + // initializer list + std::vector v6{1, 2, 4, 5}; // with elements 1, 2, 4, 5 + std::vector v7 = {1, 2, 3, 5}; // with elements 1, 2, 3, 5 + // iterators + std::vector v8(v7.begin(), v7.end()); // same as v7 + std::cout << std::endl; +} + +void vector_reallocation() { + std::cout << __func__ << ':' << std::endl; + std::vector v; + size_t capacity = v.capacity(); + + std::cout << v.data() << std::endl; // int* (address) + std::cout << v.size() << ' ' << v.capacity() << std::endl; + for (int i = 0; i < 1000; ++i) { + v.push_back(i); + if (capacity < v.capacity()) { + capacity = v.capacity(); + + std::cout << v.data() << std::endl; // int* (address) + std::cout << v.size() << ' ' << v.capacity() << std::endl; + } + } + std::cout << std::endl; +} + +void vector_iterating() { + std::cout << __func__ << ':' << std::endl; + + std::vector v = {1, 2, 3, 5}; + std::cout << "range_based for:" << std::endl; + for (auto vi : v) { + std::cout << vi << " "; + } + std::cout << std::endl; + + std::cout << "using [idx]:" << std::endl; + for (size_t i = 0; i < v.size(); ++i) { + std::cout << v[i] << " "; + } + std::cout << std::endl; + + std::cout << "forward iterating:" << std::endl; + for (auto it = v.begin(); it != v.end(); ++it) { + std::cout << *it << " "; + } + std::cout << std::endl; + + std::cout << "reverse iterating:" << std::endl; + for (auto it = v.rbegin(); it != v.rend(); ++it) { + std::cout << *it << " "; + } + std::cout << std::endl; + + std::cout << std::endl; +} + +void vector_reserve() { + std::cout << __func__ << ':' << std::endl; + std::vector v; + v.reserve(100); + for (size_t i = 0; i < 100; ++i) { + v.push_back(i); + } + std::cout << std::endl; + + std::cout << std::endl; +} + +int main() { + vector_init(); + vector_reallocation(); + vector_iterating(); + vector_reserve(); +} + + diff --git a/03_week/tasks/CMakeLists.txt b/03_week/tasks/CMakeLists.txt new file mode 100644 index 00000000..e240c8ea --- /dev/null +++ b/03_week/tasks/CMakeLists.txt @@ -0,0 +1,9 @@ +add_subdirectory(data_stats) +add_subdirectory(unique) +add_subdirectory(range) +add_subdirectory(minmax) +add_subdirectory(find_all) +add_subdirectory(os_overload) +add_subdirectory(easy_compare) +add_subdirectory(filter) +add_subdirectory(enum_operators) \ No newline at end of file diff --git a/03_week/tasks/data_stats/CMakeLists.txt b/03_week/tasks/data_stats/CMakeLists.txt new file mode 100644 index 00000000..08a1ec38 --- /dev/null +++ b/03_week/tasks/data_stats/CMakeLists.txt @@ -0,0 +1 @@ +add_gtest(test_data_stats test.cpp) \ No newline at end of file diff --git a/03_week/tasks/data_stats/README.md b/03_week/tasks/data_stats/README.md new file mode 100644 index 00000000..d2580e59 --- /dev/null +++ b/03_week/tasks/data_stats/README.md @@ -0,0 +1,13 @@ +[average]: https://en.wikipedia.org/wiki/Average +[standard deviation]: https://en.wikipedia.org/wiki/Standard_deviation + +# Статистика данных + +Необходимо реализовать функцию `CalculateDataStats`, которая принимает на вход +контейнер `std::vector` и возвращает структуру `DataStats`, содержащую +среднее значение ([average]) и стандартное отклонение ([standard deviation]). + +## Примечание + +- Запрещено использовать алгоритмы из `` +- Рекомендуется решить задачу в один проход по контейнеру \ No newline at end of file diff --git a/03_week/tasks/data_stats/data_stats.cpp b/03_week/tasks/data_stats/data_stats.cpp new file mode 100644 index 00000000..ab422d4d --- /dev/null +++ b/03_week/tasks/data_stats/data_stats.cpp @@ -0,0 +1,29 @@ +#include +#include + +struct DataStats { + double avg = 0.0; + double sd = 0.0; +}; + +DataStats CalculateDataStats(const std::vector& values) { + DataStats data; + + if (values.empty()) { + return data; + } + + for (const auto& value : values) { + data.avg += value; + } + + data.avg /= values.size(); + + for (const auto& value : values) { + data.sd += std::pow(value - data.avg, 2); + } + + data.sd = std::sqrt(data.sd / values.size()); + + return data; +} diff --git a/03_week/tasks/data_stats/test.cpp b/03_week/tasks/data_stats/test.cpp new file mode 100644 index 00000000..7061f5f4 --- /dev/null +++ b/03_week/tasks/data_stats/test.cpp @@ -0,0 +1,80 @@ +#include + +#include +#include + +#include "data_stats.cpp" + +constexpr double EPSILON = 1e-6; + +TEST(DataStatsTest, EmptyVector) { + std::vector empty_vec; + DataStats stats = CalculateDataStats(empty_vec); + + EXPECT_DOUBLE_EQ(stats.avg, 0.0); + EXPECT_DOUBLE_EQ(stats.sd, 0.0); +} + +TEST(DataStatsTest, SingleElement) { + std::vector vec = {42}; + DataStats stats = CalculateDataStats(vec); + + EXPECT_DOUBLE_EQ(stats.avg, 42.0); + EXPECT_DOUBLE_EQ(stats.sd, 0.0); +} + +TEST(DataStatsTest, TwoElements) { + std::vector vec = {10, 20}; + DataStats stats = CalculateDataStats(vec); + + EXPECT_DOUBLE_EQ(stats.avg, 15.0); + EXPECT_DOUBLE_EQ(stats.sd, 5.0); +} + +TEST(DataStatsTest, AllSameElements) { + std::vector vec = {7, 7, 7, 7, 7}; + DataStats stats = CalculateDataStats(vec); + + EXPECT_DOUBLE_EQ(stats.avg, 7.0); + EXPECT_DOUBLE_EQ(stats.sd, 0.0); +} + +TEST(DataStatsTest, PositiveNumbers) { + std::vector vec = {1, 2, 3, 4, 5}; + DataStats stats = CalculateDataStats(vec); + + EXPECT_DOUBLE_EQ(stats.avg, 3.0); + EXPECT_NEAR(stats.sd, std::sqrt(2.0), EPSILON); +} + +TEST(DataStatsTest, NegativeNumbers) { + std::vector vec = {-5, -4, -3, -2, -1}; + DataStats stats = CalculateDataStats(vec); + + EXPECT_DOUBLE_EQ(stats.avg, -3.0); + EXPECT_NEAR(stats.sd, std::sqrt(2.0), EPSILON); +} + +TEST(DataStatsTest, MixedPositiveNegative) { + std::vector vec = {-2, -1, 0, 1, 2}; + DataStats stats = CalculateDataStats(vec); + + EXPECT_DOUBLE_EQ(stats.avg, 0.0); + EXPECT_NEAR(stats.sd, std::sqrt(2.0), EPSILON); +} + +TEST(DataStatsTest, ConstCorrectness) { + const std::vector vec = {-2, -1, 0, 1, 2}; + DataStats stats = CalculateDataStats(vec); + + EXPECT_DOUBLE_EQ(stats.avg, 0.0); + EXPECT_NEAR(stats.sd, std::sqrt(2.0), EPSILON); +} + +TEST(DataStatsTest, LargeNumbers) { + std::vector vec = {50000, 60000, 70000}; + DataStats stats = CalculateDataStats(vec); + + EXPECT_DOUBLE_EQ(stats.avg, 60000.0); + EXPECT_NEAR(stats.sd, std::sqrt(200000000.0 / 3.0), EPSILON); +} \ No newline at end of file diff --git a/03_week/tasks/easy_compare/CMakeLists.txt b/03_week/tasks/easy_compare/CMakeLists.txt new file mode 100644 index 00000000..f1c0b634 --- /dev/null +++ b/03_week/tasks/easy_compare/CMakeLists.txt @@ -0,0 +1 @@ +add_gtest(test_easy_compare test.cpp) \ No newline at end of file diff --git a/03_week/tasks/easy_compare/README.md b/03_week/tasks/easy_compare/README.md new file mode 100644 index 00000000..11b1e3f1 --- /dev/null +++ b/03_week/tasks/easy_compare/README.md @@ -0,0 +1,25 @@ +# Оператор сравнения + +Необходимо реализовать перегрузку оператор сравнения для структуры `Date`. +Операторы сравнения для структуры `Date` должен соответствовать обычной логике. + +Необходимо реализовать перегрузку операторов сравнения на меньше и на равно +для структуры `StudentInfo`. Сравнение производится для ранжирования участников +олимпиады, поэтому есть особые правила. + +Две структуры равны, если равны оценки и равны баллы. + +Сравнение на меньше происходит в следующем порядке: + +1. Сравнивается оценка `mark`. Высшая оценка в приоритете (`Z` < `D` < `C` < `B` < `A`). +1. Сравниваются баллы `score`. Больший балл в приоритете (80 < 100). +1. Сравнивается курс обучения `course`. Менее опытные в приоритете (4 < 3). +1. Сравнивается дата рождения, сначала год, затем месяц, затем день. + Более молодые в приоритете. + +Сравнение начинается с первого пункта и продолжается если элементы равны. + +## Примечание + +- Удобно воспользоваться функцией `std::tie` +- В задаче гарантируется корректность данных diff --git a/03_week/tasks/easy_compare/easy_compare.cpp b/03_week/tasks/easy_compare/easy_compare.cpp new file mode 100644 index 00000000..0273a922 --- /dev/null +++ b/03_week/tasks/easy_compare/easy_compare.cpp @@ -0,0 +1,48 @@ +#include +#include + +struct Date { + unsigned year = 0; + unsigned month = 0; + unsigned day = 0; +}; + +struct StudentInfo { + size_t id; + char mark; + int score; + unsigned course; + Date birth_date; +}; + +bool operator==(const Date& lhs, const Date& rhs) { + return std::tie(lhs.year, lhs.month, lhs.day) == std::tie(rhs.year, rhs.month, rhs.day); +} + +bool operator<(const Date& lhs, const Date& rhs) { + return std::tie(lhs.year, lhs.month, lhs.day) < std::tie(rhs.year, rhs.month, rhs.day); +} + +bool operator!=(const Date& lhs, const Date& rhs) { + return !(lhs == rhs); +} + +bool operator>(const Date& lhs, const Date& rhs) { + return !((lhs < rhs) || (lhs == rhs)); +} + +bool operator<=(const Date& lhs, const Date& rhs) { + return !(lhs > rhs); +} + +bool operator>=(const Date& lhs, const Date& rhs) { + return !(lhs < rhs); +} + +bool operator==(const StudentInfo& lhs, const StudentInfo& rhs) { + return std::tie(lhs.score, lhs.mark) == std::tie(rhs.score, rhs.mark); +} + +bool operator<(const StudentInfo& lhs, const StudentInfo& rhs) { + return std::tie(rhs.mark, lhs.score, rhs.course, lhs.birth_date) < std::tie(lhs.mark, rhs.score, lhs.course, rhs.birth_date); +} \ No newline at end of file diff --git a/03_week/tasks/easy_compare/test.cpp b/03_week/tasks/easy_compare/test.cpp new file mode 100644 index 00000000..1701e7d6 --- /dev/null +++ b/03_week/tasks/easy_compare/test.cpp @@ -0,0 +1,269 @@ +#include + +#include +#include + +#include "easy_compare.cpp" + + +TEST(DateTest, DefaultCtor) { + Date date; + EXPECT_EQ(date.year, 0u); + EXPECT_EQ(date.month, 0u); + EXPECT_EQ(date.day, 0u); +} + +TEST(DateTest, ParameterizedCtor) { + Date date{2025, 12, 31}; + EXPECT_EQ(date.year, 2025u); + EXPECT_EQ(date.month, 12u); + EXPECT_EQ(date.day, 31u); +} + +TEST(DateTest, EqualityOperator) { + EXPECT_TRUE(Date({2007, 1, 1}) == Date({2007, 1, 1})); + EXPECT_FALSE(Date({2007, 1, 1}) == Date({2007, 1, 2})); + EXPECT_FALSE(Date({2007, 1, 1}) == Date({2007, 2, 1})); + EXPECT_FALSE(Date({2007, 1, 1}) == Date({2006, 1, 1})); +} + +TEST(DateTest, LessThanOperator) { + Date d1(2007, 1, 1); + Date d2(2007, 1, 2); + Date d3(2007, 2, 1); + Date d4(2008, 1, 1); + + EXPECT_TRUE(d1 < d2); + EXPECT_TRUE(d1 < d3); + EXPECT_TRUE(d1 < d4); + EXPECT_TRUE(d2 < d3); + EXPECT_TRUE(d2 < d4); + EXPECT_TRUE(d3 < d4); + + EXPECT_FALSE(d1 < d1); + EXPECT_FALSE(d2 < d1); + EXPECT_FALSE(d3 < d1); + EXPECT_FALSE(d4 < d1); + EXPECT_FALSE(d3 < d2); + EXPECT_FALSE(d4 < d3); +} + +TEST(DateTest, FullComparisonOperators) { + Date d1(2000, 1, 1); + Date d2(2000, 1, 2); + + EXPECT_TRUE(d1 != d2); + EXPECT_FALSE(d1 != d1); + + EXPECT_FALSE(d2 <= d1); + EXPECT_TRUE(d1 <= d2); + EXPECT_FALSE(d1 >= d2); + EXPECT_TRUE(d2 >= d1); + EXPECT_FALSE(d1 > d2); + EXPECT_TRUE(d2 > d1); + + EXPECT_TRUE(d1 >= d1); + EXPECT_TRUE(d1 <= d1); +} + +TEST(StudentInfoTest, EqualityOperator) { + StudentInfo s1{1, 'A', 100, 1, Date{2000, 1, 1}}; + StudentInfo s2{2, 'A', 100, 2, Date{1999, 12, 31}}; + StudentInfo s3{3, 'B', 100, 1, Date{2000, 1, 1}}; + StudentInfo s4{4, 'A', 90, 1, Date{2000, 1, 1}}; + + EXPECT_TRUE(s1 == s2); + EXPECT_FALSE(s1 == s3); + EXPECT_FALSE(s1 == s4); +} + +TEST(StudentInfoTest, NotEqualOperator) { + StudentInfo s1{1, 'A', 100, 1, Date{2000, 1, 1}}; + StudentInfo s2{4, 'B', 100, 1, Date{2000, 1, 1}}; + StudentInfo s4{2, 'A', 90, 1, Date{2000, 1, 1}}; + + EXPECT_TRUE(s1 != s2); + EXPECT_FALSE(s1 != s1); + EXPECT_TRUE(s1 != s4); +} + +TEST(StudentInfoTest, CompareByMark) { + StudentInfo z_student{1, 'Z', 10, 1, Date{2000, 1, 1}}; + StudentInfo d_student{2, 'D', 20, 2, Date{2001, 1, 1}}; + StudentInfo c_student{3, 'C', 30, 3, Date{2002, 1, 1}}; + StudentInfo b_student{2, 'B', 40, 4, Date{2003, 1, 1}}; + StudentInfo a_student{5, 'A', 50, 5, Date{2004, 1, 1}}; + + EXPECT_TRUE(z_student < d_student); + EXPECT_TRUE(z_student < c_student); + EXPECT_TRUE(z_student < b_student); + EXPECT_TRUE(z_student < a_student); + + EXPECT_TRUE(d_student < c_student); + EXPECT_TRUE(d_student < b_student); + EXPECT_TRUE(d_student < a_student); + + EXPECT_TRUE(c_student < b_student); + EXPECT_TRUE(c_student < a_student); + + EXPECT_TRUE(b_student < a_student); + + EXPECT_FALSE(a_student < z_student); + EXPECT_FALSE(a_student < d_student); + EXPECT_FALSE(a_student < c_student); + EXPECT_FALSE(a_student < b_student); +} + +TEST(StudentInfoTest, CompareByScore) { + StudentInfo high_score{3, 'A', 100, 1, Date{2000, 1, 1}}; + StudentInfo medium_score{2, 'A', 80, 2, Date{2001, 1, 1}}; + StudentInfo low_score{7, 'A', 60, 3, Date{2002, 1, 1}}; + + EXPECT_TRUE(low_score < medium_score); + EXPECT_TRUE(medium_score < high_score); + EXPECT_TRUE(low_score < high_score); + + EXPECT_FALSE(high_score < medium_score); + EXPECT_FALSE(high_score < low_score); + EXPECT_FALSE(medium_score < low_score); +} + +TEST(StudentInfoTest, CompareByCourse) { + StudentInfo first_year{1, 'A', 100, 1, Date{2000, 1, 1}}; + StudentInfo second_year{3, 'A', 100, 2, Date{2000, 1, 1}}; + StudentInfo third_year{2, 'A', 100, 3, Date{2000, 1, 1}}; + StudentInfo fourth_year{4, 'A', 100, 4, Date{2000, 1, 1}}; + + EXPECT_TRUE(fourth_year < third_year); + EXPECT_TRUE(fourth_year < second_year); + EXPECT_TRUE(fourth_year < first_year); + + EXPECT_TRUE(third_year < second_year); + EXPECT_TRUE(third_year < first_year); + + EXPECT_TRUE(second_year < first_year); + + EXPECT_FALSE(first_year < second_year); + EXPECT_FALSE(first_year < third_year); + EXPECT_FALSE(first_year < fourth_year); + EXPECT_FALSE(third_year < fourth_year); +} + +TEST(StudentInfoTest, CompareByBirthDate) { + StudentInfo young{1, 'A', 100, 1, Date{2001, 1, 1}}; + StudentInfo middle{2, 'A', 100, 1, Date{2000, 6, 15}}; + StudentInfo old{3, 'A', 100, 1, Date{1999, 12, 31}}; + + EXPECT_TRUE(old < middle); + EXPECT_TRUE(middle < young); + EXPECT_TRUE(old < young); + + EXPECT_FALSE(young < middle); + EXPECT_FALSE(young < old); + EXPECT_FALSE(middle < old); +} + +TEST(StudentInfoTest, CompareByDateComponents) { + StudentInfo year1{1, 'A', 100, 1, Date{2000, 1, 1}}; + StudentInfo year2{2, 'A', 100, 1, Date{2001, 1, 1}}; + + StudentInfo month1{4, 'A', 100, 1, Date{2000, 1, 1}}; + StudentInfo month2{3, 'A', 100, 1, Date{2000, 2, 1}}; + + StudentInfo day1{5, 'A', 100, 1, Date{2000, 1, 1}}; + StudentInfo day2{6, 'A', 100, 1, Date{2000, 1, 2}}; + + EXPECT_TRUE(year1 < year2); + EXPECT_FALSE(year2 < year1); + + EXPECT_TRUE(month1 < month2); + EXPECT_FALSE(month2 < month1); + + EXPECT_TRUE(day1 < day2); + EXPECT_FALSE(day2 < day1); +} + +TEST(StudentInfoTest, ComplexComparisons) { + StudentInfo a_student{1, 'A', 50, 4, Date{1990, 1, 1}}; + StudentInfo b_student{2, 'B', 100, 1, Date{2005, 1, 1}}; + + EXPECT_TRUE(b_student < a_student); + EXPECT_FALSE(a_student < b_student); + + StudentInfo high_score{4, 'B', 100, 4, Date{1990, 1, 1}}; + StudentInfo low_score{3, 'B', 50, 1, Date{2005, 1, 1}}; + + EXPECT_TRUE(low_score < high_score); + EXPECT_FALSE(high_score < low_score); + + StudentInfo first_year{5, 'C', 80, 1, Date{1995, 1, 1}}; + StudentInfo fourth_year{6, 'C', 80, 4, Date{2005, 1, 1}}; + + EXPECT_TRUE(fourth_year < first_year); + EXPECT_FALSE(first_year < fourth_year); + + StudentInfo older{8, 'D', 70, 2, Date{1998, 1, 1}}; + StudentInfo younger{7, 'D', 70, 2, Date{2000, 1, 1}}; + + EXPECT_TRUE(older < younger); + EXPECT_FALSE(younger < older); + + StudentInfo older_month{8, 'D', 70, 2, Date{2007, 1, 31}}; + StudentInfo younger_month{7, 'D', 70, 2, Date{2007, 2, 1}}; + + EXPECT_TRUE(older_month < younger_month); + EXPECT_FALSE(younger_month < older_month); + + StudentInfo older_day{8, 'D', 70, 2, Date{2007, 1, 1}}; + StudentInfo younger_day{7, 'D', 70, 2, Date{2007, 1, 2}}; + + EXPECT_TRUE(older_day < younger_day); + EXPECT_FALSE(younger_day < older_day); +} + +class StudentSortTest : public ::testing::Test { +protected: + void SetUp() override { + students_expected = { + {7, 'Z', 100, 1, Date{2018, 1, 1}}, + {6, 'D', 120, 2, Date{2017, 1, 1}}, + {5, 'C', 140, 3, Date{2016, 1, 1}}, + {4, 'B', 150, 5, Date{2017, 1, 1}}, + {8, 'A', 1999, 2, Date{2003, 1, 1}}, + {9, 'A', 1999, 1, Date{2003, 1, 1}}, + {3, 'A', 1999, 1, Date{2003, 2, 1}}, + {2, 'A', 1999, 1, Date{2004, 2, 1}}, + {1, 'A', 2000, 5, Date{2000, 1, 1}}, + {10, 'A', 2000, 4, Date{2000, 1, 1}}, + {11, 'A', 2000, 4, Date{2000, 1, 2}} + }; + } + + std::vector students_expected; +}; + +TEST_F(StudentSortTest, SortStudents) { + std::vector students = { + {1, 'A', 2000, 5, Date{2000, 1, 1}}, + {2, 'A', 1999, 1, Date{2004, 2, 1}}, + {3, 'A', 1999, 1, Date{2003, 2, 1}}, + {4, 'B', 150, 5, Date{2017, 1, 1}}, + {5, 'C', 140, 3, Date{2016, 1, 1}}, + {6, 'D', 120, 2, Date{2017, 1, 1}}, + {7, 'Z', 100, 1, Date{2018, 1, 1}}, + {8, 'A', 1999, 2, Date{2003, 1, 1}}, + {9, 'A', 1999, 1, Date{2003, 1, 1}}, + {10, 'A', 2000, 4, Date{2000, 1, 1}}, + {11, 'A', 2000, 4, Date{2000, 1, 2}} + }; + + std::sort(students.begin(), students.end()); + + for (size_t i = 0; i < students.size(); ++i) { + EXPECT_EQ(students[i].id, students_expected[i].id); + EXPECT_EQ(students[i].mark, students_expected[i].mark); + EXPECT_EQ(students[i].score, students_expected[i].score); + EXPECT_EQ(students[i].course, students_expected[i].course); + EXPECT_EQ(students[i].birth_date, students_expected[i].birth_date); + } +} \ No newline at end of file diff --git a/03_week/tasks/enum_operators/CMakeLists.txt b/03_week/tasks/enum_operators/CMakeLists.txt new file mode 100644 index 00000000..efb136e1 --- /dev/null +++ b/03_week/tasks/enum_operators/CMakeLists.txt @@ -0,0 +1 @@ +add_gtest(test_enum_operators test.cpp) \ No newline at end of file diff --git a/03_week/tasks/enum_operators/README.md b/03_week/tasks/enum_operators/README.md new file mode 100644 index 00000000..59f6ff0d --- /dev/null +++ b/03_week/tasks/enum_operators/README.md @@ -0,0 +1,19 @@ +# Переопределение побитовых операторов + +Необходимо реализовать перегрузку побитовых операторов для перечисления `CheckFlags`: + +- `operator|` - возвращает результат побитового ИЛИ двух перечислений +- `operator&` - возвращает `true`, если все активные флаги одного операнда + присутствуют у другого операнда, иначе возвращает `false`. В случае когда у одного + из операндов нет активных флагов возвращает `false` +- `operator^` - возвращает результат исключающего ИЛИ двух перечислений. +- `operator~` - инвертирует установленные флаги (для `NONE` результат будет `ALL`) +- `operator<<` - выводит через запятую в произвольный поток вывода имена включенных + флагов проверки + +## Ограничения + +Все операторы должны гарантировать, что результат не содержит флагов вне диапазона +`[NONE, ALL]`, даже если входные данные превышают диапазон. Флаги вне диапазона +игнорируются. + \ No newline at end of file diff --git a/03_week/tasks/enum_operators/enum_operators.cpp b/03_week/tasks/enum_operators/enum_operators.cpp new file mode 100644 index 00000000..0cd949cc --- /dev/null +++ b/03_week/tasks/enum_operators/enum_operators.cpp @@ -0,0 +1,96 @@ +#include +#include +#include + +enum class CheckFlags : uint8_t { + NONE = 0, + TIME = (1 << 0), + DATE = (1 << 1), + USER = (1 << 2), + CERT = (1 << 3), + KEYS = (1 << 4), + DEST = (1 << 5), + ALL = TIME | DATE | USER | CERT | KEYS | DEST +}; + +CheckFlags operator|(const CheckFlags& lhs, const CheckFlags& rhs) { + uint8_t lhs_u = static_cast(lhs); + uint8_t rhs_u = static_cast(rhs); + uint8_t all_u = static_cast(CheckFlags::ALL); + + return static_cast((lhs_u | rhs_u) & all_u); +} + +bool operator&(const CheckFlags& lhs, const CheckFlags& rhs) { + uint8_t all_u = static_cast(CheckFlags::ALL); + uint8_t none_u = static_cast(CheckFlags::NONE); + + uint8_t lhs_u = static_cast(lhs) & all_u; + uint8_t rhs_u = static_cast(rhs) & all_u; + + if ((lhs_u == none_u) || (rhs_u == none_u)) { + return lhs_u & rhs_u; + } + + return ((lhs_u & rhs_u) == rhs_u) || ((rhs_u & lhs_u) == lhs_u); +} + +CheckFlags operator^(const CheckFlags& lhs, const CheckFlags& rhs) { + uint8_t lhs_u = static_cast(lhs); + uint8_t rhs_u = static_cast(rhs); + uint8_t all_u = static_cast(CheckFlags::ALL); + + return static_cast((lhs_u ^ rhs_u) & all_u); +} + +CheckFlags operator~(const CheckFlags& other) { + uint8_t other_u = static_cast(other); + uint8_t all_u = static_cast(CheckFlags::ALL); + + return static_cast((~other_u) & all_u); +} + +std::ostream& operator<<(std::ostream& lhs, const CheckFlags& rhs) { + uint8_t all_u = static_cast(CheckFlags::ALL); + uint8_t rhs_u = static_cast(rhs) & all_u; + + if (rhs_u == static_cast(CheckFlags::NONE)) { + return lhs << "NONE"; + } + + std::string flags = ""; + + for (uint8_t i = 1; i < all_u; i <<= 1) { + switch (static_cast(rhs_u & i)) { + case CheckFlags::TIME: + flags += "TIME, "; + break; + case CheckFlags::DATE: + flags += "DATE, "; + break; + case CheckFlags::USER: + flags += "USER, "; + break; + case CheckFlags::CERT: + flags += "CERT, "; + break; + case CheckFlags::KEYS: + flags += "KEYS, "; + break; + case CheckFlags::DEST: + flags += "DEST, "; + break; + case CheckFlags::NONE: + break; + case CheckFlags::ALL: + break; + } + } + + if (!flags.empty()) { + flags.pop_back(); + flags.pop_back(); + } + + return lhs << flags; +} diff --git a/03_week/tasks/enum_operators/test.cpp b/03_week/tasks/enum_operators/test.cpp new file mode 100644 index 00000000..caf96643 --- /dev/null +++ b/03_week/tasks/enum_operators/test.cpp @@ -0,0 +1,226 @@ +#include + +#include +#include +#include + +#include "enum_operators.cpp" + + +TEST(EnumOperatorsTest, UnderlyingType) { + static_assert(std::is_same_v, uint8_t>, + "CheckFlags must use uint8_t as underlying type"); + EXPECT_EQ(sizeof(CheckFlags), sizeof(uint8_t)); + EXPECT_EQ(alignof(CheckFlags), alignof(uint8_t)); +} + +TEST(EnumOperatorsTest, BitwiseOr) { + EXPECT_EQ(CheckFlags::TIME | CheckFlags::DATE, static_cast( + static_cast(CheckFlags::TIME) + static_cast(CheckFlags::DATE))); + EXPECT_EQ(CheckFlags::TIME | CheckFlags::DEST | CheckFlags::USER, static_cast( + static_cast(CheckFlags::TIME) + static_cast(CheckFlags::DEST) + + static_cast(CheckFlags::USER))); + EXPECT_EQ(CheckFlags::TIME | CheckFlags::TIME, CheckFlags::TIME); + EXPECT_EQ(CheckFlags::NONE | CheckFlags::TIME, CheckFlags::TIME); + EXPECT_EQ(CheckFlags::ALL | CheckFlags::TIME, CheckFlags::ALL); +} + +TEST(EnumOperatorsTest, BitwiseOrOutside) { + EXPECT_EQ(static_cast(0xFF) | CheckFlags::NONE, CheckFlags::ALL); + uint8_t invalid = 0x40; + invalid += static_cast(CheckFlags::CERT); + EXPECT_EQ(static_cast(invalid) | CheckFlags::KEYS, + static_cast(static_cast(CheckFlags::CERT) + static_cast(CheckFlags::KEYS))); +} + +TEST(EnumOperatorsTest, BitwiseAnd) { + CheckFlags flags = CheckFlags::TIME | CheckFlags::DATE; + + EXPECT_TRUE(flags & CheckFlags::TIME); + EXPECT_TRUE(flags & CheckFlags::DATE); + EXPECT_FALSE(flags & CheckFlags::USER); + + EXPECT_TRUE(CheckFlags::ALL & CheckFlags::TIME); + EXPECT_TRUE(CheckFlags::TIME & CheckFlags::ALL); + EXPECT_TRUE(CheckFlags::DATE & CheckFlags::ALL); + EXPECT_TRUE(CheckFlags::ALL & CheckFlags::ALL); + + EXPECT_FALSE(CheckFlags::DATE & CheckFlags::NONE); + EXPECT_FALSE(CheckFlags::NONE & CheckFlags::DATE); + EXPECT_FALSE(CheckFlags::NONE & CheckFlags::ALL); + EXPECT_FALSE(CheckFlags::ALL & CheckFlags::NONE); + EXPECT_FALSE(CheckFlags::NONE & CheckFlags::NONE); +} + +TEST(EnumOperatorsTest, BitwiseAndComplex) { + CheckFlags flags = CheckFlags::TIME | CheckFlags::DATE | CheckFlags::USER; + + EXPECT_TRUE(flags & (CheckFlags::TIME | CheckFlags::DATE)); + EXPECT_TRUE(flags & (CheckFlags::USER | CheckFlags::DATE)); + EXPECT_TRUE((CheckFlags::TIME | CheckFlags::DATE) & flags); + EXPECT_TRUE((CheckFlags::USER | CheckFlags::DATE) & flags); + + EXPECT_FALSE(flags & CheckFlags::CERT); + EXPECT_FALSE(flags & (CheckFlags::TIME | CheckFlags::CERT)); + EXPECT_FALSE(flags & (CheckFlags::DATE | CheckFlags::CERT)); + EXPECT_FALSE(flags & (CheckFlags::DATE | CheckFlags::TIME | CheckFlags::CERT)); + + EXPECT_FALSE(flags & CheckFlags::NONE); + EXPECT_FALSE(CheckFlags::NONE & flags); + + EXPECT_TRUE(flags & flags); + EXPECT_TRUE(flags & (CheckFlags::TIME | CheckFlags::DATE | CheckFlags::USER | CheckFlags::CERT)); + EXPECT_TRUE((CheckFlags::TIME | CheckFlags::DATE | CheckFlags::USER | CheckFlags::CERT) & flags); +} + +TEST(EnumOperatorsTest, BitwiseAndOutside) { + EXPECT_FALSE(static_cast(0xC0) & static_cast(0xC0)); + EXPECT_FALSE(static_cast(0xC1) & static_cast(0xC2)); + EXPECT_FALSE(static_cast(0xC0) & CheckFlags::ALL); + EXPECT_TRUE(static_cast(0xC1) & static_cast(0x03)); +} + +TEST(EnumOperatorsTest, BitwiseXor) { + CheckFlags flags1 = CheckFlags::TIME | CheckFlags::DATE; + CheckFlags flags2 = CheckFlags::TIME | CheckFlags::USER; + + EXPECT_EQ(flags1 ^ flags2, CheckFlags::DATE | CheckFlags::USER); + EXPECT_EQ(flags2 ^ flags1, CheckFlags::DATE | CheckFlags::USER); + EXPECT_EQ(flags1 ^ flags1, CheckFlags::NONE); + EXPECT_EQ(flags1 ^ CheckFlags::NONE, flags1); + EXPECT_EQ(CheckFlags::NONE ^ flags2, flags2); + EXPECT_EQ(flags1 ^ CheckFlags::ALL, CheckFlags::USER | CheckFlags::CERT | CheckFlags::KEYS | CheckFlags::DEST); + EXPECT_EQ(CheckFlags::ALL ^ flags2, CheckFlags::DATE | CheckFlags::CERT | CheckFlags::KEYS | CheckFlags::DEST); +} + +TEST(EnumOperatorsTest, BitwiseXorOutside) { + EXPECT_EQ(static_cast(0xC0) ^ static_cast(0x80), CheckFlags::NONE); + EXPECT_EQ(static_cast(0xC1) ^ static_cast(0xC2), CheckFlags::TIME | CheckFlags::DATE); + EXPECT_EQ(static_cast(0xC0) ^ CheckFlags::ALL,CheckFlags::ALL); + EXPECT_EQ(static_cast(0xFF) ^ CheckFlags::ALL, CheckFlags::NONE); +} + +TEST(EnumOperatorsTest, BitwiseNot) { + EXPECT_EQ(~(CheckFlags::NONE), CheckFlags::ALL); + EXPECT_EQ(~(CheckFlags::ALL), CheckFlags::NONE); + + CheckFlags not_time = ~CheckFlags::TIME; + EXPECT_NE(not_time, CheckFlags::NONE); + EXPECT_NE(not_time, CheckFlags::TIME); + EXPECT_FALSE(not_time & CheckFlags::TIME); + + CheckFlags flags = CheckFlags::TIME | CheckFlags::DATE; + EXPECT_EQ(~(~flags), flags); +} + +TEST(EnumOperatorsTest, OutputOperator) { + std::stringstream ss; + + ss << CheckFlags::NONE; + EXPECT_EQ(ss.str(), "NONE"); + ss.str(""); + + ss << CheckFlags::ALL; + EXPECT_EQ(ss.str(), "TIME, DATE, USER, CERT, KEYS, DEST"); + ss.str(""); + + ss << CheckFlags::DEST; + EXPECT_EQ(ss.str(), "DEST"); + ss.str(""); + + ss << (CheckFlags::TIME | CheckFlags::DATE | CheckFlags::USER); + std::string result = ss.str(); + EXPECT_EQ(result, "TIME, DATE, USER"); +} + +TEST(EnumOperatorsTest, OutputOperatorOutside) { + std::stringstream ss; + + ss << static_cast(0xC1); + EXPECT_EQ(ss.str(), "TIME"); + ss.str(""); + + ss << static_cast(0xFF); + EXPECT_EQ(ss.str(), "TIME, DATE, USER, CERT, KEYS, DEST"); + ss.str(""); + + ss << static_cast(0x80); + EXPECT_EQ(ss.str(), "NONE"); + ss.str(""); +} + +TEST(EnumOperatorsTest, OutputOperatorChainUse) { + std::stringstream ss; + ss << CheckFlags::CERT << " " << CheckFlags::KEYS << " " << CheckFlags::DEST; + std::string result = ss.str(); + EXPECT_EQ(result, "CERT KEYS DEST"); +} + + +TEST(EnumOperatorsTest, Sanitization) { + uint8_t raw_value = 0xFF; + CheckFlags flags_with_invalid = static_cast(raw_value); + + CheckFlags or_result = flags_with_invalid | CheckFlags::NONE; + EXPECT_EQ(or_result, CheckFlags::ALL); + + EXPECT_TRUE(flags_with_invalid & CheckFlags::ALL); + + CheckFlags xor_result = flags_with_invalid ^ CheckFlags::ALL; + EXPECT_EQ(xor_result, CheckFlags::NONE); + std::cerr << flags_with_invalid << std::endl; + + CheckFlags not_result = ~flags_with_invalid; + std::cerr << not_result << std::endl; + EXPECT_EQ(not_result, CheckFlags::NONE); +} + +TEST(EnumOperatorsTest, CombinedOperations) { + CheckFlags base = CheckFlags::TIME | CheckFlags::DATE | CheckFlags::USER; + + CheckFlags added = base | CheckFlags::CERT; + EXPECT_TRUE(added & CheckFlags::CERT); + + CheckFlags removed = added ^ CheckFlags::DATE; + EXPECT_FALSE(removed & CheckFlags::DATE); + EXPECT_TRUE(removed & CheckFlags::TIME); + EXPECT_TRUE(removed & CheckFlags::USER); + EXPECT_TRUE(removed & CheckFlags::CERT); + + CheckFlags inverse = ~removed; + EXPECT_FALSE(inverse & CheckFlags::TIME); + EXPECT_FALSE(inverse & CheckFlags::USER); + EXPECT_FALSE(inverse & CheckFlags::CERT); + EXPECT_TRUE(inverse & CheckFlags::DATE); + EXPECT_TRUE(inverse & CheckFlags::KEYS); + EXPECT_TRUE(inverse & CheckFlags::DEST); +} + +TEST(EnumOperatorsTest, OperationWithConst) { + const CheckFlags base = CheckFlags::TIME | CheckFlags::DATE | CheckFlags::USER; + + const CheckFlags added_lhs = base | CheckFlags::CERT; + const CheckFlags added_rhs = CheckFlags::CERT | base; + EXPECT_TRUE(added_lhs & CheckFlags::CERT); + EXPECT_TRUE(CheckFlags::CERT & added_rhs); + + const CheckFlags removed_lhs = added_lhs ^ CheckFlags::DATE; + const CheckFlags removed_rhs = CheckFlags::DATE ^ added_lhs; + EXPECT_FALSE(removed_lhs & CheckFlags::DATE); + EXPECT_TRUE(removed_lhs & CheckFlags::TIME); + EXPECT_TRUE(CheckFlags::USER & removed_lhs); + EXPECT_TRUE(CheckFlags::CERT & removed_lhs); + + EXPECT_FALSE(removed_rhs & CheckFlags::DATE); + EXPECT_TRUE(removed_rhs & CheckFlags::TIME); + EXPECT_TRUE(CheckFlags::USER & removed_rhs); + EXPECT_TRUE(CheckFlags::CERT & removed_rhs); + + const CheckFlags inverse = ~removed_lhs; + EXPECT_FALSE(inverse & CheckFlags::TIME); + EXPECT_FALSE(inverse & CheckFlags::USER); + EXPECT_FALSE(inverse & CheckFlags::CERT); + EXPECT_TRUE(inverse & CheckFlags::DATE); + EXPECT_TRUE(inverse & CheckFlags::KEYS); + EXPECT_TRUE(inverse & CheckFlags::DEST); +} \ No newline at end of file diff --git a/03_week/tasks/filter/CMakeLists.txt b/03_week/tasks/filter/CMakeLists.txt new file mode 100644 index 00000000..e5f4644d --- /dev/null +++ b/03_week/tasks/filter/CMakeLists.txt @@ -0,0 +1 @@ +add_gtest(test_filter test.cpp) \ No newline at end of file diff --git a/03_week/tasks/filter/README.md b/03_week/tasks/filter/README.md new file mode 100644 index 00000000..4cd5bace --- /dev/null +++ b/03_week/tasks/filter/README.md @@ -0,0 +1,11 @@ +# Фильтрация данных + +Необходимо реализовать функцию `Filter`, которая принимает `std::vector` +и указатель на функцию предикат, и оставляет в контейнере элементы, +удовлетворяющие предикату, в исходном порядке. + +## Примечание + +- Запрещено использовать функции из `` +- Алгоритм должен работать за линейное время `O(n)` (за один проход) +- Постарайтесь не выделять дополнительную память, дополнительная память `O(1)` \ No newline at end of file diff --git a/03_week/tasks/filter/filter.cpp b/03_week/tasks/filter/filter.cpp new file mode 100644 index 00000000..869292a1 --- /dev/null +++ b/03_week/tasks/filter/filter.cpp @@ -0,0 +1,19 @@ +#include + +void Filter(std::vector& vec, bool (*f)(int)) { + if (vec.empty() || !f) { + return; + } + + std::vector filtered; + + filtered.reserve(vec.size()); + + for (const auto& value : vec) { + if (f(value)) { + filtered.push_back(value); + } + } + + vec = filtered; +} \ No newline at end of file diff --git a/03_week/tasks/filter/test.cpp b/03_week/tasks/filter/test.cpp new file mode 100644 index 00000000..2ad4ff38 --- /dev/null +++ b/03_week/tasks/filter/test.cpp @@ -0,0 +1,172 @@ +#include + +#include +#include +#include +#include +#include + +#include "filter.cpp" + + +bool IsEven(int x) { + return x % 2 == 0; +} + +bool IsOdd(int x) { + return x % 2; +} + +bool IsPositive(int x) { + return x > 0; +} + +bool AlwaysFalse(int) { + return false; +} + +TEST(FilterTest, EmptyVector) { + std::vector empty_vec; + Filter(empty_vec, IsEven); + EXPECT_TRUE(empty_vec.empty()); +} + +TEST(FilterTest, AllElementsFalse) { + std::vector vec = {1, -3, 5, -7, 9}; + Filter(vec, IsEven); + EXPECT_TRUE(vec.empty()); +} + +TEST(FilterTest, NullptrPredicate) { + std::vector vec = {1, 2, 3, 4, 5}; + size_t expected_size = vec.size(); + EXPECT_NO_THROW(Filter(vec, nullptr)); + EXPECT_EQ(vec.size(), expected_size); +} + +TEST(FilterTest, SingleElementTrue) { + std::vector vec = {18}; + Filter(vec, IsEven); + EXPECT_EQ(vec.size(), 1); + EXPECT_EQ(vec[0], 18); +} + +TEST(FilterTest, SingleElementFalse) { + std::vector vec = {17}; + Filter(vec, IsEven); + EXPECT_TRUE(vec.empty()); +} + +TEST(FilterTest, AllElementsPass) { + std::vector vec = {2, 4, 6, 8, 10}; + Filter(vec, IsEven); + std::vector expected = {2, 4, 6, 8, 10}; + EXPECT_EQ(vec.size(), expected.size()); + EXPECT_EQ(vec, expected); +} + +TEST(FilterTest, MixedElements) { + std::vector vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + Filter(vec, IsEven); + std::vector expected = {2, 4, 6, 8, 10}; + EXPECT_EQ(vec.size(), expected.size()); + EXPECT_EQ(vec, expected); +} + +TEST(FilterTest, NotSortedElements) { + std::vector vec = {10, 2, 3, 4, 5, 7, 6, 8, 9, 1}; + Filter(vec, IsEven); + std::vector expected = {10, 2, 4, 6, 8}; + EXPECT_EQ(vec, expected); +} + +TEST(FilterTest, NotRealocate) { + std::vector vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + auto capacity = vec.capacity(); + auto ptr_before = vec.data(); + Filter(vec, IsEven); + EXPECT_EQ(vec.data(), ptr_before); + EXPECT_EQ(vec.capacity(), capacity); +} + +TEST(FilterTest, AllFalsePredicate) { + std::vector vec = {1, 2, 3, 4, 5}; + Filter(vec, AlwaysFalse); + EXPECT_TRUE(vec.empty()); +} + +TEST(FilterTest, LargeVector) { + const size_t SIZE = 1'000'000; + std::vector vec(SIZE); + for (size_t i = 0; i < SIZE; ++i) { + vec[i] = static_cast(i); + } + + Filter(vec, IsEven); + + EXPECT_EQ(vec.size(), SIZE / 2); + + for (size_t i = 0; i < vec.size(); ++i) { + EXPECT_EQ(vec[i], static_cast(i * 2)); + EXPECT_TRUE(IsEven(vec[i])); + } +} + +TEST(FilterTest, ConsecutiveCalls) { + std::vector vec = {1, -2, 3, 4, 5, 6, 7, -8, 9, 10}; + + Filter(vec, IsEven); + Filter(vec, IsEven); + std::vector expected = {-2, 4, 6, -8, 10}; + EXPECT_EQ(vec, expected); + + Filter(vec, IsPositive); + expected = {4, 6, 10}; + EXPECT_EQ(vec, expected); +} + +TEST(FilterTest, BigRandomDataMultipleSeeds) { + const size_t NUM_TESTS = 10; + const size_t VECTOR_SIZE = 1'000'000; + const int MIN_VALUE = -1'000'000; + const int MAX_VALUE = 1'000'000; + + std::random_device rd; + std::mt19937 seed_gen(rd()); + std::uniform_int_distribution seed_dist(0, 100000); + + double erase_duration = 0; + double filter_duration = 0; + for (size_t test_idx = 0; test_idx < NUM_TESTS; ++test_idx) { + unsigned int seed = seed_dist(seed_gen); + std::mt19937 gen(seed); + std::uniform_int_distribution value_dist(MIN_VALUE, MAX_VALUE); + + std::vector test(VECTOR_SIZE); + for (size_t i = 0; i < VECTOR_SIZE; ++i) { + test[i] = value_dist(gen); + } + + auto expected = test; + auto start = std::chrono::high_resolution_clock::now(); + std::erase_if(expected, IsOdd); + auto end = std::chrono::high_resolution_clock::now(); + erase_duration += std::chrono::duration_cast(end - start).count(); + + start = std::chrono::high_resolution_clock::now(); + Filter(test, IsEven); + end = std::chrono::high_resolution_clock::now(); + filter_duration += std::chrono::duration_cast(end - start).count(); + + ASSERT_EQ(test.size(), expected.size()) + << "Test #" << test_idx << " with seed " << seed + << ": size mismatch"; + EXPECT_EQ(test, expected) + << "Test #" << test_idx << " with seed " << seed + << ": content mismatch"; + } + + EXPECT_LE(filter_duration, 1.2 * erase_duration) + << "Function too slow: " + <<"filter_duration = " << filter_duration << " ms, erase_duration = " << erase_duration << " ms\n"; +} \ No newline at end of file diff --git a/03_week/tasks/find_all/CMakeLists.txt b/03_week/tasks/find_all/CMakeLists.txt new file mode 100644 index 00000000..e96cdff7 --- /dev/null +++ b/03_week/tasks/find_all/CMakeLists.txt @@ -0,0 +1 @@ +add_gtest(test_find_all test.cpp) \ No newline at end of file diff --git a/03_week/tasks/find_all/README.md b/03_week/tasks/find_all/README.md new file mode 100644 index 00000000..50475494 --- /dev/null +++ b/03_week/tasks/find_all/README.md @@ -0,0 +1,9 @@ +# Найти все элементы + +Необходимо реализовать функцию `FindAll`, которая принимает на вход контейнер +`std::vector` и указатель на функцию предикат, и возвращает контейнер +позиций всех элементов, удовлетворяющих предикату. + +## Примечание + +- Запрещено использовать функции из `` \ No newline at end of file diff --git a/03_week/tasks/find_all/find_all.cpp b/03_week/tasks/find_all/find_all.cpp new file mode 100644 index 00000000..e5200507 --- /dev/null +++ b/03_week/tasks/find_all/find_all.cpp @@ -0,0 +1,27 @@ +#include + +auto FindAll(const std::vector& vec, bool (*f)(int)) { + std::vector res; + + if (vec.empty() || !f) { + return res; + } + + size_t count = 0; + + for (size_t i = 0; i < vec.size(); ++i) { + if (f(vec[i])) { + ++count; + } + } + + res.reserve(count); + + for (size_t i = 0; i < vec.size(); ++i) { + if (f(vec[i])) { + res.push_back(i); + } + } + + return res; +} \ No newline at end of file diff --git a/03_week/tasks/find_all/test.cpp b/03_week/tasks/find_all/test.cpp new file mode 100644 index 00000000..f58dc607 --- /dev/null +++ b/03_week/tasks/find_all/test.cpp @@ -0,0 +1,119 @@ +#include + +#include + +#include "find_all.cpp" + +bool IsEven(int x) { + return x % 2 == 0; +} + +bool IsPositive(int x) { + return x > 0; +} + +bool AlwaysFalse(int) { + return false; +} + +TEST(FindAllTest, EmptyVector) { + std::vector empty_vec; + auto result = FindAll(empty_vec, IsEven); + EXPECT_TRUE(result.empty()); +} + +TEST(FindAllTest, NullptrPredicate) { + std::vector vec = {1, 2, 3, 4, 5}; + auto result = FindAll(vec, nullptr); + EXPECT_TRUE(result.empty()); +} + +TEST(FindAllTest, SingleElementTrue) { + std::vector vec = {18}; + auto result = FindAll(vec, IsEven); + EXPECT_EQ(result.size(), 1); + EXPECT_EQ(result[0], 0); +} + +TEST(FindAllTest, SingleElementFalse) { + std::vector vec = {17}; + auto result = FindAll(vec, IsEven); + EXPECT_TRUE(result.empty()); +} + +TEST(FindAllTest, AllElementsPass) { + std::vector vec = {2, 4, 6, 8, 10}; + auto result = FindAll(vec, IsEven); + + EXPECT_EQ(result.size(), vec.size()); + std::vector expected = {0, 1, 2, 3, 4}; + EXPECT_EQ(result, expected); +} + +TEST(FindAllTest, AllElementsFalse) { + std::vector vec = {1, 3, 5, 7, 9}; + auto result = FindAll(vec, IsEven); + EXPECT_TRUE(result.empty()); +} + +TEST(FindAllTest, MixedElements) { + std::vector vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + auto result = FindAll(vec, IsEven); + std::vector expected = {1, 3, 5, 7, 9}; + EXPECT_EQ(result, expected); +} + +TEST(FindAllTest, NotSortedElements) { + std::vector vec = {10, 2, 3, 4, 5, 7, 6, 8, 9, 1}; + auto result = FindAll(vec, IsEven); + std::vector expected = {0, 1, 3, 6, 7}; + EXPECT_EQ(result, expected); +} + +TEST(FindAllTest, AllFalsePredicate) { + std::vector vec = {1, 2, 3, 4, 5}; + auto result = FindAll(vec, AlwaysFalse); + EXPECT_TRUE(result.empty()); +} + +TEST(FindAllTest, LargeVector) { + const size_t SIZE = 1'000'000; + std::vector vec(SIZE); + for (size_t i = 0; i < SIZE; ++i) { + vec[i] = static_cast(i); + } + + auto result = FindAll(vec, IsEven); + + EXPECT_EQ(result.size(), SIZE / 2); + + for (size_t i = 0; i < result.size(); ++i) { + EXPECT_EQ(result[i], i * 2); + EXPECT_TRUE(IsEven(vec[result[i]])); + } +} + +TEST(FindAllTest, CapacityOptimization) { + std::vector vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + + auto result = FindAll(vec, IsEven); + EXPECT_EQ(result.capacity(), result.size()); +} + +TEST(FindAllTest, ConsecutiveCalls) { + std::vector vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + + auto result1 = FindAll(vec, IsEven); + auto result2 = FindAll(vec, IsEven); + EXPECT_EQ(result1, result2); + + auto result3 = FindAll(vec, IsPositive); + EXPECT_EQ(result3.size(), 10); +} + +TEST(FindAllTest, ConstCorrectness) { + const std::vector vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + auto result = FindAll(vec, IsEven); + std::vector expected = {1, 3, 5, 7, 9}; + EXPECT_EQ(result, expected); +} \ No newline at end of file diff --git a/03_week/tasks/minmax/CMakeLists.txt b/03_week/tasks/minmax/CMakeLists.txt new file mode 100644 index 00000000..a2a33c75 --- /dev/null +++ b/03_week/tasks/minmax/CMakeLists.txt @@ -0,0 +1 @@ +add_gtest(test_minmax test.cpp) \ No newline at end of file diff --git a/03_week/tasks/minmax/README.md b/03_week/tasks/minmax/README.md new file mode 100644 index 00000000..b9067359 --- /dev/null +++ b/03_week/tasks/minmax/README.md @@ -0,0 +1,14 @@ +# Минимум и максимум + +Необходимо реализовать функцию `MinMax`, которая принимает на вход контейнер +`std::vector` и возвращает пару итераторов, указывающих на минимальный +и максимальный элемент контейнера. + +## Примечание + +- Запрещено использовать `std::minmax_element` и другие функции из `` +- Необходимо найти оба элемента за один проход по контейнеру. +- Для пустого контейнера необходимо вернуть пару итераторов на конец контейнера. +- В случае нескольких одинаковых элементов, итератор на минимум должен указывать + на первое вхождение, итератор на максимум на последнее вхождение соответствующих + элементов \ No newline at end of file diff --git a/03_week/tasks/minmax/minmax.cpp b/03_week/tasks/minmax/minmax.cpp new file mode 100644 index 00000000..c434e189 --- /dev/null +++ b/03_week/tasks/minmax/minmax.cpp @@ -0,0 +1,23 @@ +#include +#include + +auto MinMax(const std::vector& vec) { + if (vec.empty()) { + return std::make_pair(vec.end(),vec.end()); + } + + auto min = vec.begin(); + auto max = vec.begin(); + + for (auto it = vec.begin(); it != vec.end(); ++it) { + if (*it < *min) { + min = it; + } + + if (*it >= *max) { + max = it; + } + } + + return std::make_pair(min, max); +} \ No newline at end of file diff --git a/03_week/tasks/minmax/test.cpp b/03_week/tasks/minmax/test.cpp new file mode 100644 index 00000000..03a4bf0a --- /dev/null +++ b/03_week/tasks/minmax/test.cpp @@ -0,0 +1,141 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "minmax.cpp" + + +TEST(MinMaxTest, EmptyVector) { + std::vector empty_vec; + auto [min_it, max_it] = MinMax(empty_vec); + + EXPECT_EQ(min_it, empty_vec.end()); + EXPECT_EQ(max_it, empty_vec.end()); +} + +TEST(MinMaxTest, SingleElement) { + std::vector vec = {42}; + auto [min_it, max_it] = MinMax(vec); + + EXPECT_EQ(min_it, vec.begin()); + EXPECT_EQ(max_it, vec.begin()); + EXPECT_EQ(*min_it, 42); + EXPECT_EQ(*max_it, 42); +} + +TEST(MinMaxTest, AllEqualElements) { + std::vector vec = {5, 5, 5, 5, 5}; + auto [min_it, max_it] = MinMax(vec); + + EXPECT_EQ(min_it, vec.begin()); + EXPECT_EQ(max_it, vec.end() - 1); + EXPECT_EQ(*min_it, 5); + EXPECT_EQ(*max_it, 5); +} + +TEST(MinMaxTest, BeginMinEndMax) { + std::vector vec = {1, 2, 3, 4, 5}; + auto [min_it, max_it] = MinMax(vec); + + EXPECT_EQ(min_it, vec.begin()); + EXPECT_EQ(max_it, vec.end() - 1); + EXPECT_EQ(*min_it, 1); + EXPECT_EQ(*max_it, 5); + +} + +TEST(MinMaxTest, BeginMaxEndMin) { + std::vector vec = {5, 4, 3, 2, 1}; + auto [min_it, max_it] = MinMax(vec); + + EXPECT_EQ(min_it, vec.end() - 1); + EXPECT_EQ(max_it, vec.begin()); + EXPECT_EQ(*min_it, 1); + EXPECT_EQ(*max_it, 5); +} + +TEST(MinMaxTest, MinAndMaxInMiddle) { + std::vector vec = {5, 3, 2, 1, 5, 4, 2, 1}; + auto [min_it, max_it] = MinMax(vec); + + EXPECT_EQ(min_it, vec.begin() + 3); + EXPECT_EQ(max_it, vec.begin() + 4); + EXPECT_EQ(*min_it, 1); + EXPECT_EQ(*max_it, 5); +} + +TEST(MinMaxTest, NegativeNumbers) { + std::vector vec = {-5, -10, -3, -1, -8}; + auto [min_it, max_it] = MinMax(vec); + + EXPECT_EQ(min_it, vec.begin() + 1); + EXPECT_EQ(max_it, vec.begin() + 3); + EXPECT_EQ(*min_it, -10); + EXPECT_EQ(*max_it, -1); +} + +TEST(MinMaxTest, MixedPositiveNegative) { + std::vector vec = {-5, 10, -3, 0, 8, -20}; + auto [min_it, max_it] = MinMax(vec); + + EXPECT_EQ(min_it, vec.end() - 1); + EXPECT_EQ(max_it, vec.begin() + 1); + EXPECT_EQ(*min_it, -20); + EXPECT_EQ(*max_it, 10); +} + +TEST(MinMaxTest, LargeVector) { + std::vector vec(1000); + + int value = -vec.size() / 2; + for (size_t i = 0; i < vec.size(); ++i, ++value) { + vec[i] = value; + } + + vec[500] = -vec.size(); + vec[750] = vec.size(); + + auto [min_it, max_it] = MinMax(vec); + + EXPECT_EQ(min_it, vec.begin() + 500); + EXPECT_EQ(max_it, vec.begin() + 750); + EXPECT_EQ(*min_it, -vec.size()); + EXPECT_EQ(*max_it, vec.size()); +} + +TEST(MinMaxTest, ConstCorrectness) { + const std::vector vec = {1, 9}; + auto [min_it, max_it] = MinMax(vec); + + EXPECT_EQ(*min_it, 1); + EXPECT_EQ(*max_it, 9); +} + +TEST(MinMaxTest, PerformanceCheck) { + const double THRESHOLD = 0.3; + const size_t SIZE = 1'000'000u; + std::vector large_vec(SIZE); + std::iota(large_vec.begin(), large_vec.end(), 0); + std::shuffle(large_vec.begin(), large_vec.end(), std::mt19937{18}); + + auto start = std::chrono::high_resolution_clock::now(); + auto [min_it, max_it] = MinMax(large_vec); + auto end = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(end - start); + + start = std::chrono::high_resolution_clock::now(); + auto [min_it_std, max_it_std] = std::minmax_element(large_vec.begin(), large_vec.end()); + end = std::chrono::high_resolution_clock::now(); + auto duration_std = std::chrono::duration_cast(end - start); + + EXPECT_LE(std::abs(duration_std.count() - duration.count()) * 1.0, duration_std.count() * THRESHOLD) << + "Your function is very slowly"; + EXPECT_EQ(*min_it, *min_it_std); + EXPECT_EQ(*max_it, *max_it_std); +} \ No newline at end of file diff --git a/03_week/tasks/os_overload/CMakeLists.txt b/03_week/tasks/os_overload/CMakeLists.txt new file mode 100644 index 00000000..48aae693 --- /dev/null +++ b/03_week/tasks/os_overload/CMakeLists.txt @@ -0,0 +1 @@ +add_gtest(test_os_overload test.cpp) \ No newline at end of file diff --git a/03_week/tasks/os_overload/README.md b/03_week/tasks/os_overload/README.md new file mode 100644 index 00000000..2986a87a --- /dev/null +++ b/03_week/tasks/os_overload/README.md @@ -0,0 +1,42 @@ +# Перегрузка оператора вывода + +Необходимо реализовать операторы вывода в поток для типа `CircleRegionList` и +всех вложенных в него типов. + +Структура `Coord2D` содержит два целочисленных поля `x`, `y`, по умолчанию, +имеющие значения начала координат. + +Структура `Circle` имеет поле `Coord2D` для центра окружности, и целочисленное поле +для задания радиуса окружности. По умолчанию радиус должен быть равен 1 и не должен +принимать отрицательных значений. + +Пара `CircleRegion` включает в себя структуру `Circle` и булевое поле, которое +характеризует внутренняя или внешняя зона окружности активна, `true` - внутренняя, +`false` - внешняя + +Требования к выводу: + +- `Coord2D` - должен выводится в формате `(x, y)` +- `Circle` - должен выводиться в формате `circle[{center}, r = {radius}]`, где + `{center}` вывод координат центра в соответствии с `Coord2D`, а `{radius}` + значение радиуса, или `circle[]`, если радиус равен нулю. +- `CircleRegion` - при выводе должен ставить `+` для внутренней зоны окружности и + `-` для внешней перед форматированным выводом `Circle` +- `CircleRegionList` - выводит внутри фигурных скобок с нулевым отступом, весь список + `CircleRegion` через запятую в форматированном виде, с новой строки, с отступом в одну + табуляцию или `{}`, если список пуст. + +Возможный вывод в поток: + +``` +{ + +circle[(-1, 3), r = 5], + -circle[(0, 0), r = 17], + +circle[], + -circle[] +} +``` + +## Примечание + +- Должен работать вывод в произвольный поток вывода. \ No newline at end of file diff --git a/03_week/tasks/os_overload/os_overload.cpp b/03_week/tasks/os_overload/os_overload.cpp new file mode 100644 index 00000000..3f0c9082 --- /dev/null +++ b/03_week/tasks/os_overload/os_overload.cpp @@ -0,0 +1,55 @@ +#include +#include +#include + +struct Coord2D { + int x = 0; + int y = 0; +}; + +struct Circle { + Coord2D coord; + unsigned radius = 1; +}; + +using CircleRegion = std::pair; +using CircleRegionList = std::vector; + +std::ostream& operator<<(std::ostream& os, const Coord2D& coord) { + return os << '(' << coord.x << ", " << coord.y << ')'; +} + +std::ostream& operator<<(std::ostream& os, const Circle& circ) { + if (circ.radius == 0) { + return os << "circle[]"; + } + + return os << "circle[" << circ.coord << ", r = " << circ.radius << "]"; +} + +std::ostream& operator<<(std::ostream& os, const CircleRegion& reg) { + os << ((reg.second) ? '+' : '-'); + return os << reg.first; +} + +std::ostream& operator<<(std::ostream& os, const CircleRegionList& list) { + if (list.empty()) { + return os << "{}"; + } + + os << "{\n"; + + for (auto it = list.begin(); it != list.end(); ++it) { + os << '\t' << *it; + + if (it != list.end() - 1) { + os << ','; + } + + os << '\n'; + } + + os << '}'; + + return os; +} \ No newline at end of file diff --git a/03_week/tasks/os_overload/test.cpp b/03_week/tasks/os_overload/test.cpp new file mode 100644 index 00000000..461009a3 --- /dev/null +++ b/03_week/tasks/os_overload/test.cpp @@ -0,0 +1,308 @@ +#include + +#include +#include + +#include "os_overload.cpp" + +TEST(Coord2DTest, DefaultConstructor) { + Coord2D coord; + EXPECT_EQ(coord.x, 0); + EXPECT_EQ(coord.y, 0); +} + +TEST(Coord2DTest, Coord2DCreation) { + Coord2D coord = {5, -3}; + EXPECT_EQ(coord.x, 5); + EXPECT_EQ(coord.y, -3); +} + +TEST(Coord2DTest, OutputOperator) { + { + Coord2D coord; + std::ostringstream oss; + oss << coord; + EXPECT_EQ(oss.str(), "(0, 0)"); + } + { + Coord2D coord{42, -15}; + std::ostringstream oss; + oss << coord; + EXPECT_EQ(oss.str(), "(42, -15)"); + } + { + Coord2D coord{-1, 3}; + std::ostringstream oss; + oss << coord; + EXPECT_EQ(oss.str(), "(-1, 3)"); + } +} + +TEST(CircleTest, DefaultConstructor) { + Circle circle; + EXPECT_EQ(circle.coord.x, 0); + EXPECT_EQ(circle.coord.y, 0); + EXPECT_EQ(circle.radius, 1u); +} + +TEST(CircleTest, CircleCreation) { + { + Coord2D center{10, 20}; + Circle circle(center, 5); + EXPECT_EQ(circle.coord.x, 10); + EXPECT_EQ(circle.coord.y, 20); + EXPECT_EQ(circle.radius, 5u); + } + { + Circle circle({-5, 7}); + EXPECT_EQ(circle.coord.x, -5); + EXPECT_EQ(circle.coord.y, 7); + EXPECT_EQ(circle.radius, 1u); + } +} + +TEST(CircleTest, OutputOperator) { + { + Circle circle; + std::ostringstream oss; + oss << circle; + EXPECT_EQ(oss.str(), "circle[(0, 0), r = 1]"); + } + { + Coord2D center(-1, 3); + Circle circle(center, 5); + std::ostringstream oss; + oss << circle; + EXPECT_EQ(oss.str(), "circle[(-1, 3), r = 5]"); + } + { + Coord2D center(0, 0); + Circle circle(center, 17); + std::ostringstream oss; + oss << circle; + EXPECT_EQ(oss.str(), "circle[(0, 0), r = 17]"); + } + { + Coord2D center(10, 10); + Circle circle(center, 0); + std::ostringstream oss; + oss << circle; + EXPECT_EQ(oss.str(), "circle[]"); + } + { + Coord2D center(100, -200); + Circle circle(center, 1000); + std::ostringstream oss; + oss << circle; + EXPECT_EQ(oss.str(), "circle[(100, -200), r = 1000]"); + } +} + +TEST(CircleRegionTest, CreateAndOutput) { + { + Circle circle({3, 4}); + CircleRegion region(circle, true); + std::ostringstream oss; + oss << region; + EXPECT_EQ(oss.str(), "+circle[(3, 4), r = 1]"); + } + { + Circle circle({-5, -6}, 7); + CircleRegion region(circle, false); + std::ostringstream oss; + oss << region; + EXPECT_EQ(oss.str(), "-circle[(-5, -6), r = 7]"); + } + { + Circle circle({1, 2}, 0); + CircleRegion region(circle, true); + std::ostringstream oss; + oss << region; + EXPECT_EQ(oss.str(), "+circle[]"); + } + { + Circle circle({1, 2}, 0); + CircleRegion region(circle, false); + std::ostringstream oss; + oss << region; + EXPECT_EQ(oss.str(), "-circle[]"); + } +} + +TEST(CircleRegionListTest, EmptyList) { + CircleRegionList empty_list; + std::ostringstream oss; + oss << empty_list; + std::string expected = "{}"; + EXPECT_EQ(oss.str(), expected); +} + +TEST(CircleRegionListTest, SingleElement) { + CircleRegionList list; + list.emplace_back(Circle(Coord2D{1, 2}, 3), true); + std::ostringstream oss; + oss << list; + std::string expected = "{\n\t+circle[(1, 2), r = 3]\n}"; + EXPECT_EQ(oss.str(), expected); +} + +TEST(CircleRegionListTest, MultipleElements) { + CircleRegionList list; + list.emplace_back(Circle(Coord2D(-1, 3), 5), true); + list.emplace_back(Circle(Coord2D(0, 0), 17), false); + list.emplace_back(Circle(Coord2D(10, 20), 0), true); + list.emplace_back(Circle(Coord2D(-5, -5), 0), false); + + std::ostringstream oss; + oss << list; + + std::string expected = + "{\n" + "\t+circle[(-1, 3), r = 5],\n" + "\t-circle[(0, 0), r = 17],\n" + "\t+circle[],\n" + "\t-circle[]\n" + "}"; + EXPECT_EQ(oss.str(), expected); +} + +TEST(CircleRegionListTest, VariousCombinations) { + CircleRegionList list; + + // Добавляем разные комбинации + list.emplace_back(Circle({0, 0}), true); + list.emplace_back(Circle(Coord2D(5, 5), 10), false); + list.emplace_back(Circle(Coord2D(-10, -10), 0), true); + list.emplace_back(Circle(Coord2D(100, 200), 50), true); + list.emplace_back(Circle(Coord2D(-3, 7), 2), false); + + std::ostringstream oss; + oss << list; + + std::string expected = + "{\n" + "\t+circle[(0, 0), r = 1],\n" + "\t-circle[(5, 5), r = 10],\n" + "\t+circle[],\n" + "\t+circle[(100, 200), r = 50],\n" + "\t-circle[(-3, 7), r = 2]\n" + "}"; + EXPECT_EQ(oss.str(), expected); +} + +TEST(FullTaskTest, ChainedOutput) { + { + Coord2D coord{42, -15}; + std::ostringstream oss; + oss << coord << coord; + EXPECT_EQ(oss.str(), "(42, -15)(42, -15)"); + } + { + Circle circle_one({0, 0}, 17); + Circle circle_two({10, 10}, 0); + std::ostringstream oss; + oss << circle_one << circle_two; + EXPECT_EQ(oss.str(), "circle[(0, 0), r = 17]circle[]"); + } + { + Circle circle({-5, -6}, 7); + CircleRegion region(circle, false); + std::ostringstream oss; + oss << region << region; + EXPECT_EQ(oss.str(), "-circle[(-5, -6), r = 7]-circle[(-5, -6), r = 7]"); + } + { + CircleRegionList list; + list.emplace_back(Circle(Coord2D(1, 2), 3), true); + + std::ostringstream oss; + oss << "List:\n" << list << '\n' << list << "\nEnd"; + + std::string expected = "List:\n{\n\t+circle[(1, 2), r = 3]\n}\n{\n\t+circle[(1, 2), r = 3]\n}\nEnd"; + EXPECT_EQ(oss.str(), expected); + } +} + +TEST(FullTaskTest, ConstCorrectness) { + { + const Coord2D coord{42, -15}; + std::ostringstream oss; + oss << coord << coord; + EXPECT_EQ(oss.str(), "(42, -15)(42, -15)"); + } + { + const Circle circle_one({0, 0}, 17); + const Circle circle_two({10, 10}, 0); + std::ostringstream oss; + oss << circle_one << circle_two; + EXPECT_EQ(oss.str(), "circle[(0, 0), r = 17]circle[]"); + } + { + Circle circle({-5, -6}, 7); + const CircleRegion region(circle, false); + std::ostringstream oss; + oss << region << region; + EXPECT_EQ(oss.str(), "-circle[(-5, -6), r = 7]-circle[(-5, -6), r = 7]"); + } + { + const CircleRegionList list = { std::make_pair(Circle(Coord2D(1, 2), 3), true) }; + + std::ostringstream oss; + oss << "List:\n" << list << '\n' << list << "\nEnd"; + + std::string expected = "List:\n{\n\t+circle[(1, 2), r = 3]\n}\n{\n\t+circle[(1, 2), r = 3]\n}\nEnd"; + EXPECT_EQ(oss.str(), expected); + } +} + +class StandardOutputCaptureTest : public ::testing::Test { +protected: + void SetUp() override { + origin_cout = std::cout.rdbuf(buffer.rdbuf()); + } + + void TearDown() override { + std::cout.rdbuf(origin_cout); + } + + std::string GetOutput() { + return buffer.str(); + } + + void ClearOutput() { + buffer.str(""); + } + + std::stringstream buffer; + std::streambuf* origin_cout; +}; + +TEST_F(StandardOutputCaptureTest, WorkWithCout) { + { + Coord2D coord{42, -15}; + EXPECT_NO_THROW(std::cout << coord); + EXPECT_EQ(GetOutput(), "(42, -15)"); + } + ClearOutput(); + { + Circle circle_one({0, 0}, 17); + Circle circle_two({10, 10}, 0); + EXPECT_NO_THROW(std::cout << circle_one << circle_two); + EXPECT_EQ(GetOutput(), "circle[(0, 0), r = 17]circle[]"); + } + ClearOutput(); + { + Circle circle({-5, -6}, 7); + CircleRegion region(circle, false); + EXPECT_NO_THROW(std::cout << region); + EXPECT_EQ(GetOutput(), "-circle[(-5, -6), r = 7]"); + } + ClearOutput(); + { + CircleRegionList list; + list.emplace_back(Circle(Coord2D(1, 2), 3), true); + EXPECT_NO_THROW(std::cout << "List:\n" << list << '\n' << list << "\nEnd"); + std::string expected = "List:\n{\n\t+circle[(1, 2), r = 3]\n}\n{\n\t+circle[(1, 2), r = 3]\n}\nEnd"; + EXPECT_EQ(GetOutput(), expected); + } +} \ No newline at end of file diff --git a/03_week/tasks/range/CMakeLists.txt b/03_week/tasks/range/CMakeLists.txt new file mode 100644 index 00000000..650a0654 --- /dev/null +++ b/03_week/tasks/range/CMakeLists.txt @@ -0,0 +1 @@ +add_gtest(test_range test.cpp) \ No newline at end of file diff --git a/03_week/tasks/range/README.md b/03_week/tasks/range/README.md new file mode 100644 index 00000000..4fb3ccad --- /dev/null +++ b/03_week/tasks/range/README.md @@ -0,0 +1,11 @@ +# Диапазон элементов + +Необходимо реализовать функцию `Range`, которая принимает на вход значение +начала диапазона, конца диапазона, шаг, и возвращает вектор `std::vector` +значений из диапазона `[from, to)` с шагом `step`. Шаг по умолчанию равен 1. + +## Примечание + +- Запрещено использовать функции из `` +- Рекомендуется избежать реалокаций памяти, то есть выделить память под нужное + количество элементов заранее \ No newline at end of file diff --git a/03_week/tasks/range/range.cpp b/03_week/tasks/range/range.cpp new file mode 100644 index 00000000..32210c0f --- /dev/null +++ b/03_week/tasks/range/range.cpp @@ -0,0 +1,33 @@ +#include +#include +#include + +std::vector Range(int from, int to, int step = 1) { + std::vector res; + + if (!step) { + return res; + } + + if ((from > to) && (step > 0)) { + return res; + } + + if ((from < to) && (step < 0)) { + return res; + } + + size_t elements = std::abs(std::ceil((to - from) / (double)step)); + + if (!elements) { + return res; + } + + res.reserve(elements); + + for (int i = from; (from < to) ? (i < to) : (i > to); i += step) { + res.push_back(i); + } + + return res; +} \ No newline at end of file diff --git a/03_week/tasks/range/test.cpp b/03_week/tasks/range/test.cpp new file mode 100644 index 00000000..1b680b90 --- /dev/null +++ b/03_week/tasks/range/test.cpp @@ -0,0 +1,98 @@ +#include + +#include +#include + +#include "range.cpp" + +TEST(RangeTest, SimpleIncreasing) { + EXPECT_EQ(Range(1, 6, 1), std::vector({1, 2, 3, 4, 5})); + EXPECT_EQ(Range(1, 7, 2), std::vector({1, 3, 5})); + EXPECT_EQ(Range(-20, 10, 10), std::vector({-20, -10, 0})); +} + +TEST(RangeTest, SimpleDecreasing) { + EXPECT_EQ(Range(6, 1, -1), std::vector({6, 5, 4, 3, 2})); + EXPECT_EQ(Range(7, 1, -2), std::vector({7, 5, 3})); + EXPECT_EQ(Range(20, 18, -3), std::vector({20})); + EXPECT_EQ(Range(-20, -24, -3), std::vector({-20, -23})); +} + +TEST(RangeTest, EmptyRanges) { + EXPECT_TRUE(Range(0, 0, 2).empty()); + EXPECT_TRUE(Range(7, 7, 1).empty()); + EXPECT_TRUE(Range(20, 20, -2).empty()); + EXPECT_TRUE(Range(-5, -5, -2).empty()); + EXPECT_TRUE(Range(-7, -7, 1).empty()); +} + +TEST(RangeTest, InvalidParameters) { + EXPECT_TRUE(Range(1, 7, -1).empty()); + EXPECT_TRUE(Range(3, 7, 0).empty()); + EXPECT_TRUE(Range(2, -2, 2).empty()); + EXPECT_TRUE(Range(3, -7, 0).empty()); + EXPECT_TRUE(Range(-2, -3, 2).empty()); +} + +TEST(RangeTest, SingleElement) { + EXPECT_EQ(Range(7, 8, 1), std::vector({7})); + EXPECT_EQ(Range(7, 8, 2), std::vector({7})); + EXPECT_EQ(Range(7, 8, 100), std::vector({7})); + + EXPECT_EQ(Range(5, 4, -1), std::vector({5})); + EXPECT_EQ(Range(5, 4, -2), std::vector({5})); + EXPECT_EQ(Range(5, 4, -100), std::vector({5})); + + EXPECT_EQ(Range(-7, 8, 100), std::vector({-7})); + EXPECT_EQ(Range(-7, -8, -100), std::vector({-7})); +} + +TEST(RangeTest, NegativeNumbers) { + EXPECT_EQ(Range(-5, 0, 1), std::vector({-5, -4, -3, -2, -1})); + EXPECT_EQ(Range(-5, 5, 2), std::vector({-5, -3, -1, 1, 3})); + EXPECT_EQ(Range(0, -5, -1), std::vector({0, -1, -2, -3, -4})); + EXPECT_EQ(Range(0, -10, -3), std::vector({0, -3, -6, -9})); +} + +TEST(RangeTest, LargeStep) { + EXPECT_EQ(Range(0, 100, 33), std::vector({0, 33, 66, 99})); + EXPECT_EQ(Range(100, 0, -33), std::vector({100, 67, 34, 1})); +} + +TEST(RangeTest, DefaultStep) { + EXPECT_EQ(Range(0, 5), std::vector({0, 1, 2, 3, 4})); + EXPECT_EQ(Range(-2, 3), std::vector({-2, -1, 0, 1, 2})); + EXPECT_TRUE(Range(5, 5).empty()); + EXPECT_TRUE(Range(5, 3).empty()); +} + +TEST(RangeTest, CapacityMatchesSize) { + auto result = Range(0, 100, 3); + EXPECT_EQ(result.capacity(), result.size()); +} + +TEST(RangePerformanceTest, LargeRange) { + const int from = -1'000'000; + const int to = 1'000'000; + + auto result_asc = Range(from, to); + EXPECT_EQ(result_asc.size(), to - from); + EXPECT_EQ(result_asc.front(), from); + EXPECT_EQ(result_asc.back(), to - 1); + + for (size_t i = 0; i < result_asc.size(); ++i) { + EXPECT_EQ(result_asc[i], from + static_cast(i)); + } + + auto result_desc = Range(to - 1, from - 1, -1); + EXPECT_EQ(result_desc.size(), to - from); + EXPECT_EQ(result_desc.front(), to - 1); + EXPECT_EQ(result_desc.back(), from); + + for (size_t i = 0; i < result_asc.size(); ++i) { + EXPECT_EQ(result_desc[i], to - 1 - static_cast(i)); + } + + EXPECT_GE(result_asc.capacity(), result_asc.size()); + EXPECT_GE(result_desc.capacity(), result_desc.size()); +} \ No newline at end of file diff --git a/03_week/tasks/unique/CMakeLists.txt b/03_week/tasks/unique/CMakeLists.txt new file mode 100644 index 00000000..3920144d --- /dev/null +++ b/03_week/tasks/unique/CMakeLists.txt @@ -0,0 +1 @@ +add_gtest(test_unique test.cpp) \ No newline at end of file diff --git a/03_week/tasks/unique/README.md b/03_week/tasks/unique/README.md new file mode 100644 index 00000000..5f5c253e --- /dev/null +++ b/03_week/tasks/unique/README.md @@ -0,0 +1,11 @@ +# Уникальные элементы + +Необходимо реализовать функцию `Unique`, которая принимает отсортированный +вектор `std::vector` и возвращает вектор уникальных элементов в исходном +порядке. + +## Примечание + +- Запрещено использовать функции из `` +- Рекомендуется избегать частых реалокаций памяти +- Вместимость вектора должна соответствовать количеству элементов \ No newline at end of file diff --git a/03_week/tasks/unique/test.cpp b/03_week/tasks/unique/test.cpp new file mode 100644 index 00000000..6c459abd --- /dev/null +++ b/03_week/tasks/unique/test.cpp @@ -0,0 +1,92 @@ +#include + +#include +#include +#include + +#include "unique.cpp" + +TEST(UniqueTest, EmptyVector) { + std::vector empty; + auto result = Unique(empty); + EXPECT_TRUE(result.empty()); + EXPECT_EQ(result.capacity(), result.size()); +} + +TEST(UniqueTest, SingleElement) { + std::vector vec = {42}; + auto result = Unique(vec); + EXPECT_EQ(result.size(), 1); + EXPECT_EQ(result[0], 42); +} + +TEST(UniqueTest, AllSameElements) { + std::vector vec = {1, 1, 1, 1, 1}; + auto result = Unique(vec); + EXPECT_EQ(result.size(), 1); + EXPECT_EQ(result[0], 1); +} + +TEST(UniqueTest, AlreadyUniqueSorted) { + std::vector vec = {1, 2, 3, 4, 5}; + auto result = Unique(vec); + EXPECT_EQ(result.size(), 5); + EXPECT_EQ(result, vec); +} + +TEST(UniqueTest, SimpleDuplicates) { + std::vector vec = {1, 2, 2, 3, 3, 3}; + auto result = Unique(vec); + std::vector expected = {1, 2, 3}; + EXPECT_EQ(result, expected); +} + +TEST(UniqueTest, MultipleDuplicates) { + std::vector vec = {1, 1, 2, 2, 2, 3, 3, 4, 5, 5, 5, 5}; + auto result = Unique(vec); + std::vector expected = {1, 2, 3, 4, 5}; + EXPECT_EQ(result, expected); +} + +TEST(UniqueTest, NegativeNumbers) { + std::vector vec = {-5, -5, -3, -3, -3, 0, 0, 2, 2, 2, 2}; + auto result = Unique(vec); + std::vector expected = {-5, -3, 0, 2}; + EXPECT_EQ(result, expected); +} + +TEST(UniqueTest, ZeroAndDuplicates) { + std::vector vec = {0, 0, 0, 0}; + auto result = Unique(vec); + std::vector expected = {0}; + EXPECT_EQ(result, expected); +} + +TEST(UniqueTest, RandomSortedData) { + std::mt19937 gen(18); + std::uniform_int_distribution dist(-1000, 1000); + + const size_t N = 1'000'000; + std::vector vec(N); + + for (size_t i = 0; i < N; ++i) { + vec[i] = dist(gen); + } + std::sort(vec.begin(), vec.end()); + std::vector expected = vec; + auto last = std::unique(expected.begin(), expected.end()); + expected.erase(last, expected.end()); + + auto result = Unique(vec); + + EXPECT_EQ(result.size(), expected.size()); + EXPECT_EQ(result.size(), result.capacity()); + EXPECT_EQ(result, expected); +} + +TEST(UniqueTest, ConstCorrectness) { + const std::vector vec = {1, 2, 2, 3, 3, 3}; + auto result = Unique(vec); + std::vector expected = {1, 2, 3}; + EXPECT_EQ(result, expected); +} \ No newline at end of file diff --git a/03_week/tasks/unique/unique.cpp b/03_week/tasks/unique/unique.cpp new file mode 100644 index 00000000..abe54f80 --- /dev/null +++ b/03_week/tasks/unique/unique.cpp @@ -0,0 +1,30 @@ +#include + +auto Unique(const std::vector& vec) { + std::vector res; + + if (vec.empty()) { + return res; + } + + size_t count = 1; + + for (size_t i = 0; i < vec.size() - 1; ++i) { + + if (vec[i] != vec[i + 1]) { + ++count; + } + } + + res.reserve(count); + res.push_back(vec[0]); + + for (size_t i = 0; i < vec.size() - 1; ++i) { + + if (vec[i] != vec[i + 1]) { + res.push_back(vec[i + 1]); + } + } + + return res; +} \ No newline at end of file diff --git a/04_week/04_class.md b/04_week/04_class.md new file mode 100644 index 00000000..46e00076 --- /dev/null +++ b/04_week/04_class.md @@ -0,0 +1,902 @@ +# Лекция 4. ООП. Класс + +1. [ООП](#oop) + - [Инкапсуляция](#encapsulation) + - [Наследование](#inheritance) + - [Полиморфизм](#polymorphism) + - [Абстракция](#abstraction) +1. [Класс](#class) + - [Спецификаторы доступа](#access_specifiers) + - [Отличие класса и структуры](#class_struct_diff) + - [Пустой класс](#class_empty) + - [Поля класса](#class_fields) + - [Инициализация полей значением по умолчанию](#field_default_init) + - [Конструктор](#ctor) + - [Список инициализации полей класса](#member_initializer_list) + - [Параметризованный конструктор](#parameterized_ctor) + - [Конструктор по умолчанию](#default_ctor) + - [Конструктор копирования](#copy_ctor) + - [Указатель на себя `this`](#this_ptr) + - [Копирующий оператор присваивания](#copy_assignment) + - [Деструктор](#dtor) + - [Конструктор преобразования](#converting_ctor) + - [Ключевое слово `explicit`](#explicit) + - [Конструктор от `std::initializer_list`(_C++11_)](#ctor_std_initializer_list) + - [Делегирующий конструктор (_C++11_)](#delegating_ctor) + - [Ключевое слово `default` (_С++11_)](#keyword_default) + - [Ключевое слово `delete` (_С++11_)](#keyword_delete) + - [Методы](#methods) + - [Определение методов вне класса](#methods_definition_outside) + - [CV-квалификация методов ](#cv_for_methods) + - [Оператор преобразования](#conversion_operator) + - [Перегрузка операторов внутри класса](#class_operator_overloading_inside) + - [Перегрузка операторов вне класса](#class_operator_overloading_outside) + - [Ключевое слово `friend`](#keyword_friend) + - [Ключевое слово `mutable`](#keyword_mutable) + + +## ООП + +Объектно-ориентированное программирование - парадигма программирования, которая +основывается на представление в коде программы различных объектов, взаимодействующих +друг с другом. + +Класс - пользовательский тип данных, шаблон (макет) для создания объектов и описания +их характеристик, функций. + +Объект - экземпляр класса. Объект включает данные (поля) и методы (функции). +Что позволяет хранить характеристики объекта, изменять их и взаимодействовать с +другими объектами. + +Основные принципы ООП: + +- Инкапсуляция +- Наследование +- Полиморфизм +- Абстракция + +### Инкапсуляция + +Инкапсуляция - объединение данных и методов для работы с данными внутри класса. +Сокрытие деталей реализации класса. + +```c++ +class Budget { +public: + void increase_balance(double value) { + budget_ += value; + } +private: + double budget_; +}; +``` + +### Наследование + +Наследование - механизм создания новых классов на основе существующих. Позволяет +строить иерархию классов и переиспользовать код классов родителей внутри +классов наследников. + +```c++ +class Animal { /* common data */}; +class Cat : public Animal {}; +class Dog : public Animal {}; +``` + +### Полиморфизм + +Полиморфизм - возможность различного поведения сущностей C++. + +Виды полиморфизма: + +- статический (на этапе компиляции, шаблоны, перегрузка функций) +- динамический (во время выполнения программы, виртуальные методы) + +```c++ +class Shape { +public: + virtual void draw() = 0; +}; +class Circle : public Shape { + void draw() override { /* рисуем круг */ } +}; +``` + +### Абстракция + +Абстракция - упрощение сложных вещей через выделение основных характеристик. + +## Класс + +Класс - пользовательский тип данных, который объединяет в себе данные (поля класса) +и функции для работы с данными (методы класса), представляет собой макет для +создания объектов (экземпляров) данного типа. + +Синтаксис: `class {};` + +- `` - имя класса, пользовательского типа данных +- `` - тело класса, включающее поля, методы, конструкторы и деструктор + +### Спецификаторы доступа + +Для ограничения видимости полей и методов внутри класса используются +спецификаторы доступа, весь код после спецификатора имеет соответствующий тип +доступа: + +- `public` - публичный доступ, поле или метод класса доступны извне +- `protected` - защищенный доступ, поля и методы доступны наследникам класса + и внутри класса +- `private` - приватный доступ, поля и методы доступны только внутри класса. + +Синтаксис внутри класса или структуры: `:` + +Указывать спецификаторы доступа можно произвольное число раз. + +```c++ +class User { +public: + /* some data and functions for everyone */ +protected: + /* some data and functions for children classes */ +private: + /* some data and functions inside class */ +}; +``` + +Приватные поля и методы будут недоступны снаружи, то есть **НЕЛЬЗЯ** к ним +обратится или вызвать через экземляр класс, используя операторы `.`, `->`. + +Всё содержимое класса по умолчанию имеет спецификатор доступа `private`, +несмотря на это часто принято явно указывать данный спецификатор, даже при +определении полей класса в самом начале тела класса. + +### Отличие класса и структуры + +Структура `struct` и класс `class` имеют одинаковые возможности в C++. + +Отличие заключается, что содержимое структуры по умолчанию имеет публичный +доступ `public`, а содержимое класса приватный `private` + +Структура нужна для взаимодействия с `legacy` кодом на языке C, а также для +публичных классов. + +Несмотря на одинаковые возможности, принято разделять структуру и класс +семантически. Так, структуру используют только с публичными полями, а класс +с приватными. Создавать классы и структуры со смешанным типом полей не +рекомендуется, так как это может быть не очевидно и не понятно программистам, +читающим код. + +### Пустой класс + +Пустой класс имеет размер 1 байт, поскольку объект такого класса можно создать +и необходимо иметь адресс данного объекта, чтобы иметь адресс, необходимо что-то +положить в память по определенному адресу. + +Чаще используется пустая структура. Такая структура может понадобитсья в качестве +именнованного тега. Пока будем просто считать, что иногда надо. + +### Поля класса + +Поля класса представляют собой внутренние переменные произвольного типа. +К полям класса внутри класса можно обращаться по имени. В качестве поля можно +использовать указатели и ссылки. + +В случае ссылок необходимо их инициализировоать при создании объекта. Например, +можно проинициализирвоать адресом объекта из глобальной области видимости. А еще +это можно сделать в списке инициализации при конструировании объекта. + +Существуют разные стили кода к именованию полей класса. Часто встречается: + +- `m_` - добавляют `m_` в качестве префикса к перименной (`m` - `member`) +- `_` - добавляют `_` в качестве постфикса к переменной. + +```c++ +// inside class +int m_value; +int value_; +``` + +Поля класса хранятся в классе и инициализируются в порядке их объявления. + +Поля уникальны для каждого экземпляра класса. + +### Инициализация полей значением по умолчанию + +Аналогично структурам рекомендуется всегда инициализировать поля внутри класса. + +```c++ +class Time { +private: + int hour_ = 0; + int minute_{0}; // uniform +}; +``` + +Иначе в полях класса также может лежать мусор. + +### Конструктор + +Конструктор это особый метод класса, который используется для конструирования +объекта. + +Синтаксис: `() {}` + +- `` - имя конструктора должно совпадать с именем класса +- `` - аргументы конструктора. +- `` - тело конструктора. + +В зависимости от аргументов конструктора выделяют различные типы конструктора. +Основные способы конструирования объекта: + +- Параметризованный конструктор +- Конструктор по умолчанию +- Конструктор копирования +- Копирующий оператор присваивания +- Конструктор перемещения +- Перемещающий оператор присваивания + +Важно понимать, что если конструкторы не определены, то компилятор самостоятельно +сгенерирует конструкторы. Но если определен, хотя бы один конструктор, то +компилятор скорее всего этого не сделает. + +Важно понимать, что при входе в тело конструктора все поля уже проинициализированы +и в теле может происходить только присваивание новых значений полям класса. +Следовательно, в теле уже нельзя изменить константное поле или инициализировать +ссылку. + +Проинициализировать константу и ссылку можно не только значением по +умолчанию или значением (адресом) переменной из глобальной области видимости. +Для этого в синтаксисе конструктора предусмотрен список инициализации. + +### Список инициализации полей класса + +Список инициализации полей (_member initializer list_) позволяет инициализировать +поля в момент создания объекта. В списке инициализации доступны аргументы +конструктора и имена полей класса. Список инициализации указывается между сигнатурой +и телом конструктора, и выглядит как перечисление после символа `:` через запятую +полей класса и внутри `()` или `{}` их инициализирующих значений. + +Синтаксис: `() : {}` + +- `` - список инициализации = `(), {}` + +```c++ +class InitList { +public: + InitList(int val) : a_(val), b_(val), c_(val) {} +public: + int a_; int b_; int c_; +}; +``` + +Причем имена полей класса и имена параметров могут полностью совпадать, конфликта +имен не будет, поскольку компилятор понимает, что нужно инициализировать поля. + +```c++ +class InitSameName { +public: + InitSameName(int a, int b, int c) : a(a), b(b), c(c) {} +public: + int a; int b; int c; +}; +``` + +Также, следует отметить, что в качестве инициализирующего значения, может использоваться +не только переменная или константа, но также выражение (_expression_) и результат +вызова функции. + +**ВАЖНО**, что инициализация происходит в порядке полей класса, и не зависит от +порядка в списке инициализации. Поэтому важно самостоятельно отслеживать +правильный порядок инициализации. + +```c++ +class BadOrderInit { +public: + BadOrderInit(int val) : c(val), b(c + 1), a(10) {} +public: + int a; int b; int c; +}; +``` +- `c` используется неинициализированной при инициализации `b` (**UB**) + +Если поля класса объявлены со значением по умолчанию, то они будут проигнорированы +для полей в списке инициализации. + +```c++ +class BadOrderInit { +public: + BadOrderInit(int val) : c(val), b(c + 1), a(10) {} +public: + int a = 7; int b = 7; int c = 7; +}; +``` +- значение `7` будет проигнорированно, по-прежнему **UB** + +Списки инициализации могут быть неполными. Тогда недостающие поля будут +сконструированы со значениями по умолчанию, а при их отсутствии инициализируются +мусором. + +```c++ +class BadOrderInitNoAB { +public: + BadOrderInitNoAB(int val) : c(val) {} +public: + int a; int b = 7; int c = 7; +}; +``` +- в поле `b` будет значение `7`, в `a` будет мусор + +Список инициализации позволяет сконструировать константное поле и поле ссылку извне: + +```c++ +class RefConst { +public: + RefConst(int value, int& ref, const double& cref) + : id_(value), ref_(ref), const_ref_(cref) {} +private: + const int id_; + int& ref_; + const double& const_ref_; +}; +``` + +### Параметризованный конструктор + +Конструктор, который имеет параметры (аргументы) называют параметризованным +конструктором (конструктором с параметрами). Аргументов может быть несколько +и они могут иметь значения по умолчанию, Таким образом, конструктор может +быть перегружен. + +```c++ +class Time { +public: + Time(int hour, int minute, int second) + : hour_(hour), minute_(minute), second_(second) {} +private: + int hour_, minute_, second_; +}; +``` + +Если конструктор имеет у всех аргументов значение по умолчанию, то такой +конструктор перегружает конструктор по умолчанию. + +```c++ +class Time { +public: + Time(int hour = 0, int minute = 0, int second = 0) + : hour_(hour), minute_(minute), second_(second) {} +private: + int hour_, minute_, second_; +}; +``` + +Для создания объекта класса необходим вызов конструктора. Синтаксис вызова констуктора: + +```c++ +Time t1(1, 1, 1); +Time t2{1, 1, 1}; +Time t3 = {1, 1, 1} +Time t4 = Time{1, 1, 1}; +Time t5 = Time(1, 1, 1); +``` + +Аналогично для всех его вариантов перегрузки. + +### Конструктор по умолчанию + +Конструктор по умолчанию представляет собой конструктор без аргументов. +Часто для простых класов конструктор имеет пустое тело. Удобно использовать +значение по умолчанию для инициализации. + +Синтаксис: `() {}` + +Часто имеет пустое тело для тривиальных случаев. + +Если не определен ни один конструктор, то компилятор самостоятельно сгенерирует +данный конструктор. + +```c++ +class DefaultCtor { +public: + DefaultCtor() {} +private: + int value = 0; +}; +``` + +Вызов конструктора: + +```c++ +DefaultCtor obj; +DefaultCtor obj2{}; +DefaultCtor obj3 = {}; +DefaultCtor obj4 = DefaultCtor{}; +DefaultCtor obj5 = DefaultCtor(); +``` +- во всех этих случаях вызовется только конструктор по умолчанию один раз + +### Конструктор копирования + +Конструктор копирования необходим для создания копии объекта из объекта того +же типа. Представляет собой конструктор, принимающий в качестве аргументов +константную ссылку того же типа, что и сам класс. + +Синтаксис: `(const & ) {}` + +```c++ +class Time { +public: + Time(const Time& other) + : hour_(other.hour_), minute_(other.minute_), second_(other.second_) {} +private: + int hour_, minute_, second_; +}; +``` + +Поля другого объекта того же класса доступны внутри методов класса даже если они +приватные. + +Вызывается конструктор копирования: + +- при передаче в функцию по значению +- при возврате объекта соответствующего значения по значению +- при конструировании одного объекта из другого + +```c++ +Time t; +Time t1 = t; // copy ctor +Time t2(t); // copy ctor +Time t3{t}; // copy ctor +``` + +### Указатель на себя `this` + +Внутри класса, в методах, в том числе конструкторах, можно получить указатель на +себя (объект класса, который вызывает данный метод) с помощью ключевого слова `this`. + +Можно использовать `this`, как в качестве значения по умолчанию, так и в списке +инициализации. + +```c++ +class Self { +public: + Self() : self(this) {}; + Self* GetPtr() { return self; } + Self& GetRef() { return *this; } +private: + Self* self = this; +}; +``` + +Можно считать что указатель на себя передается первым неявным аргументом в конструкторы, +методы и операторы класса. + +Через указатель можно явно обращаться к полям класса, но как правило, так не делают + +```c++ +// inside methods +this->self; +``` + +### Копирующий оператор присваивания + +Оператор присвания необходим при присваивании одного созданного объекта другому. +Если один из объектов не создан, то он не будет вызываться, а будет вызываться +конструктор копирования, даже если в инструкции есть `=`. + +Как правило, оператор возвращает ссылку на себя (экземпляр текущего класса), что +позволяет испоьзовать цепочку из операторов `=`. Для этого необходимо вернуть из +оператора разыменованный указатель на себя `return *this;`. + +Синтаксис: `& operator=(const & ) {}` + +Поскольку язык не запрещает присвоить объект самому себе, как правило, в копирующем +операторе присваивания выполняют проверку на самоприсваивание. Особенно это +критично для классов владеющих ресурсами (выделяющих память), что может привести +к **UB** + +```c++ +class Time { +public: + Time& operator=(const Time& other) { + if (this == &other) { + return *this; + } + hour_ = other.hour_; + minute_ = other.minute_; + second_ = other.second_; + return *this; + } +private: + int hour_, minute_, second_; +}; +``` + +Вызов оператора: + +```c++ +Time t1, t2, t3; +t1 = t2; // copy assignment +t1 = t1; // copy assignment +t1 = t2 = t3; // copy assignment +auto t4 = t1; // copy ctor (not a copy assignment!) +``` + +### Деструктор + +Особый метод, вызываемый перед разрушением объекта, когда заканчивается время +жизни объекта. + +Синтаксис: `~() {}` + +Если в конструкторе выполнялось ручное выделение ресурсов, то в деструкторе +необходимо обязательно освободить ресурсы. Иначе деструктор остается тривиальным +и генерируется компилятором по умолчанию. + +Деструкторы вызываются в обратном порядке по отношению к конструируемым объектам +при выходе из области видимости. Последний сконструированный объект, будет разрушен +первым. + +### Конструктор преобразования + +Конструктором преобразования называется конструктор, принимающий один аргумент +другого произвольного типа. Данный конструктор разрешает неявное преобразование +из указанного типа в тип класса. + +```c++ +class Integer { +private: + int value; +public: + Integer(int v) : value(v) {} + Integer(char c) : value(static_cast(c)) {} +}; +``` + +Таким образом, если функция принимает пользовательский класс, а класс имеет +конструктор преобразования от другого типа, то в функцию можно передать +непосредственно этот другой тип, произойдет неявное преобразование с помощью +соответствующего конструктора: + +```c++ +int DoSomething(Integer i) {} + +int main() { + Integer i{3}; + int value = 5; + char c = 'I'; + DoSomething(i); // OK + DoSomething(value); // OK + DoSomething(5); // OK + DoSomething(c); // OK + DoSomething('i'); // OK +} +``` + +**ВАЖНО** понимать, что при наличии конструктора присваивания из другого типа, +компилятор **НЕ** будет генеировать оператор присваивания из данного типа, его +необходимо определять самостоятельно. + +### Ключевое слово `explicit` + +Ключевое слово `explicit` используется как спецификатор перед именем конструктора +и позволяет запретить неявное преобразование и сообщает компилятору, что данный +конструктор можно вызывать только явно. + +```c++ +class Integer { +private: + int value; +public: + Integer(int v) : value(v) {} + Integer(char c) : value(static_cast(c)) {} + explicit Integer(double d) : value(static_cast(d)) {} +}; +``` + +Неявно такой конструктор вызвать нельзя: + +```c++ +//Integer i2 = 3.14; // compile error +Integer i3 = Integer{3.14}; // OK + +int DoSomething(Integer i) {} + +int main() { + double d = 3.14; + //DoSomething(d); // compile error + //DoSomething(3.14); // compile error + DoSomething(Integer{3.14}); // OK + DoSomething(Integer(3.14)); // OK +} +``` + +Также спецификатор `explicit` можно использовать с оператором преобразования, об этом +после знакомства с методами. + +### Конструктор от `std::initializer_list`(_C++11_) + +В C++11 появился контейнер список инициализации `std::initializer_list`, который +позволяет инициализировать класс набором элементов. Что вызывает неоднозначность +при наличии параметризированных конструкторов какой конструктор вызывать. + +Конструктор по умолчанию имеет приоритет перед конструктором от списка инициализации. + +Список инициализации имеет приоритет перед параметризированными конструкторами при +использовании `{}`. + +```c++ +class Vector { +public: + Vector() {}; + Vector(size_t count); + Vector(int a, int b); + Vector(std::initializer_list list); +private: + std::vector data; +}; +``` + +Вызов конструкторов: + +```c++ +Vector v = {1, 2, 3, 4, 5}; // ctor std::initializer_list +Vector v2{1, 2, 3}; // ctor std::initializer_list +Vector v3(10); // ctor Vector(size_t) +Vector v4{10}; // ctor std::initializer_list +Vector v5 = {10}; // ctor std::initializer_list +Vector v6(10, 20); // ctor Vector(int a, int b) +Vector v7{10, 20}; // ctor std::initializer_list +Vector v8 = {10, 20}; // ctor std::initializer_list +Vector v9 = 10; // ctor Vector(size_t) implicit cast +Vector v10; // default ctor +Vector v11{}; // default ctor +Vector v12 = {}; // default ctor +``` + +### Делегирующий конструктор (_C++11_) + +Делегирующий конструктор - конструктор, который на месте списка инициализации +использует другой конструктор данного класса. В таком случае можно указать только +один целевой конструктор, дополнительно списки инициализации указать нельзя. + +```c++ +class Time { +public: + Time(int hour, int minute, int second) + : hour_(hour), minute_(minute), second_(second) {} + Time(int hour) : Time(hour, 0, 0) {} +private: + int hour_, minute_, second_; +}; +``` + +Делегирующий конструктор **НЕ** может быть рекурсивным. + +### Ключевое слово `default` (_С++11_) + +С помощью ключевого слова `default` можно явно попросить компилятор сгенерировать +конструктор (деструктор), указав после сигнатуры `= default`. Это более выразительно, +чем писать `{}` для конструктора по умолчанию. Рекомендуется к использованию. + +```c++ +class Value { +public: + Value(int x) : x_(x) {} + Value() = default; + Value(const Value& other) = default; + Value(Value&& other) = default; + Value& operator=(const Value& other) = default; + Value& operator=(Value&& other) = default; + ~Value() = default; +private: + int x = 0; +}; +``` + +### Ключевое слово `delete` (_С++11_) + +С помощью ключевого слова `delete` можно явно попросить компилятор удалить функцию +(запретить её использование), указав после сигнатуры `= delete`. Это более выразительно, +чем прятать конструкторы в приватную область класса. Рекомендуется к использованию. + +Можно использовать не только для конструкторов, деструкторов, но и для любых методов, +операторов, шаблонных функций, функций вне классов. + +```c++ +class Value { +public: + Value(int x) : x_(x) {} + Value() = delete; + Value(const Value&) = delete; + Value& operator=(const Value&) = delete; +private: + int x = 0; +}; +``` + +Например, если класс не подразумевает сравнения на равенство или других операторов +можно явно указать для них `delete`. + +### Методы + +Внутри класса можно определять функции, которые могут работать с полями класса, в том +числе закрытыми. Данные функции называются методы. + +Синтаксис аналогичен определению обычным функциям. + +Публичный метод можно вызвать через операторы `.` для экземпляра и `->` для указателяю. + +Приватные методы, можно вызывать внутри класса. + +Можно вызывать методы в списках инициализации. Например, метод, который будет +контролировать допустимость значения или выполнять дополнительные преобразования. + +### Определение методов вне класса + +Методы можно объявить внутри класса, а определить снаружи класса. Содержимое +класса имеет свою область видимости. Для определения снаружи класса перед именем +конструктора, метода, оператора используется имя класса и оператор разрешения области +видимости `::` + +```c++ +class Time { +public: + Time(); + Time(int hours, int minutes, int seconds = 0); + int GetHours(); + void SetHours(int hours); +private: + int hours_ = 0; + int minutes_ = 0; + int seconds_ = 0; +}; + +Time::Time() = default; +Time::Time(int hours, int minutes, int seconds) + : hours_(hours), minutes_(minutes), seconds_(seconds) {} + +int Time::GetHours() { return hours; } +void Time::SetHours(int hours) { hours_ += hours; } +``` + +Аргументы методов, имеющие значения по умолчанию указываются только при объявлении, +при определении нельзя указать значения по умолчанию + +### CV-квалификация методов + +Методы могут иметь CV-квалификацию. Методы, которые не изменяют полей класса, а только +предоставляют информацию о них следует помечать квалификатором `const` после сигнатуры +и перед телом метода: + +```c++ +class Size { +public: + size_t GetSize() const { return size; }; + void AddSize(size_t size) { size_ += size; }; +private: + size_t size_ = 0; +}; +``` + +Методы помеченные квалификатором `const` можно вызывать у константных объектов класса. +Компилятор отслеживает, что в данном методе нет измененеий полей класса. + +Методы можно перегрузить только по квалификатору `const`. + +Не изменяет поля класса и может быть вызван для константного объекта: + +```c++ +int Class::foo() const; +``` + +Может изменять поля класса и может быть вызван для `volatile` объекта: + +```c++ +int Class::foo() volatile; +``` + +Может быть вызван как для `const`, так и для `volatile` объекта, так и для +`const volatile` объекта: + +```c++ +int Class::foo() const volatile; +``` + +### Оператор преобразования + +В классе возможно определить оператор преобразования, который позволяет преобразовывать +пользовательский класс в другой тип. + +Синтаксис: ` operator () const {}` + +- `` - можно запретить неявное преобразование +- `` - тип к которому выполняется приведение + +Рекомендуется помечать `const` поскольку данный оператор не должен менять полей класса +и вызываться от констант данного класса. + +Как правило рекомендуется запрещать неявное преобразование к типу (использовать +`explicit`), поскольку можно обнаружить много неожиданных мест в коде, где неявно +произведено преобразование. + +Исключением обычно является оператор `bool` для удобства использования в условиях. + +### Перегрузка операторов внутри класса + +Поскольку первым аргументом неявно передается ключевое слово `this`, то перегрузка +бинарных операторов внутри класса имеет один аргумент: + +```c++ +Class& operator+=(const Class& other); +Class& operator-=(const Class& other); +Class& operator*=(const Class& other); +Class& operator/=(const Class& other); +``` + +Операторы арифмесстических операций часто переопределяют на основе работы присваивающих +операторов: + +```c++ +Class operator+(const Class& other) const { + Class result = *this; // copy ctor + result += other; // operator += + return result; +} +``` + +Операторы префиксного и постфиксного инкремента/декремента переопределяются +следующим образом: + +```c++ +Class& operator++(); // ++obj +Class operator++(int); // obj++ +Class& operator--(); // --obj +Class operator--(int); // obj-- +``` +- постфиксный оператор возвращает копию, поэтому у возвращаемого значения нет `&` + +### Перегрузка операторов вне класса + +Операторы можно перегрузить вне класса, тогда сигнатура перегружаемого оператора +пишется в привычной манере. Но для реализации таких операторов у класса должны быть +методы задающие и считывающие значение полей (геттеры и сеттеры). Бывает, что их нет, +тогда перегрузить класс не получится или получится на основе определенных операторов +составного присваивания внутри класса. + +Перегрузка инкремента и декремента вне класса: + +```c++ +Class& operator++(const Class& obj); // ++obj +Class operator++(const Class& obj, int); // obj++ +Class& operator--(const Class& obj); // --obj +Class operator--(const Class& obj, int); // obj-- +``` + +### Ключевое слово `friend` + +Внутри класса с помощью ключевого слова `friend` (_friend declaration_) можно +объявить дружественную функцию, класс или дружественный метод другого класса. + +Сущности объявленные дружественными будут иметь доступ к `private` и `protected` +полям класса. + +Дружественность работает в одностороннем порядке. + +```c++ +friend void SomeMethod(int); +friend struct SomeStruct; +friend class SomeClass; +friend OtherClass; // C++11 +friend int OtherClass::Method(); +``` + +### Ключевое слово `mutable` + +Спецификатор типа `mutable` разрешает изменять поле класса, объявленное с ним, +даже в константных методах и для константных объектов. + +Например, это может быть поле представляющее собой счетчик операций и необходимо его +изменять даже в константном методе. + +Также может использоваться в лямбда-выражениях \ No newline at end of file diff --git a/04_week/04_class.pdf b/04_week/04_class.pdf new file mode 100644 index 00000000..50a488d9 Binary files /dev/null and b/04_week/04_class.pdf differ diff --git a/04_week/CMakeLists.txt b/04_week/CMakeLists.txt new file mode 100644 index 00000000..b15ca37c --- /dev/null +++ b/04_week/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.14) # Минимальная требуемая версия CMake +project(04_week) # Необходим для инициализации cmake + +set(CMAKE_CXX_STANDARD 20) # Версия стандарта C++ +set(CMAKE_CXX_STANDARD_REQUIRED ON) # Гарантирует использование указанной версии стандарта + +set(EXAMPLES_DIR examples) # Определим переменную с именем директории +set(TASKS_DIR tasks) + +add_subdirectory(tasks) + +# Создать исполняемый файл для каждого примера +if (BUILD_EXAMPLES_04_WEEK) +# add_example(class_examples ${EXAMPLES_DIR}/class_examples.cpp) +endif() \ No newline at end of file diff --git a/04_week/tasks/CMakeLists.txt b/04_week/tasks/CMakeLists.txt new file mode 100644 index 00000000..6ddf92b4 --- /dev/null +++ b/04_week/tasks/CMakeLists.txt @@ -0,0 +1,4 @@ +add_subdirectory(stack) +add_subdirectory(queue) +add_subdirectory(ring_buffer) +add_subdirectory(phasor) \ No newline at end of file diff --git a/04_week/tasks/phasor/CMakeLists.txt b/04_week/tasks/phasor/CMakeLists.txt new file mode 100644 index 00000000..933d4fae --- /dev/null +++ b/04_week/tasks/phasor/CMakeLists.txt @@ -0,0 +1 @@ +add_gtest(test_phasor test.cpp) \ No newline at end of file diff --git a/04_week/tasks/phasor/README.md b/04_week/tasks/phasor/README.md new file mode 100644 index 00000000..cd6f4453 --- /dev/null +++ b/04_week/tasks/phasor/README.md @@ -0,0 +1,90 @@ +[Комплексная амплитуда]: https://en.wikipedia.org/wiki/Phasor + +# Комплексная амплитуда + +Необходимо реализовать класс `Phasor`, представляющий комплексную амплитуду. +[Комплексная амплитуда] это комплексное число, представляющее синусоидальную +функцию фиксированной частоты с определенной амплитудой и начальной фазой, +не меняющимися во времени. + +Класс предоставляет следующие конструкторы: + +- Конструктор по умолчанию создает нулевой фазор. +- Конструктор, принимающий амплитуду и фазу в радианах. + +Конструкторы с тегами `ExpTag`, `DegTag`, `AlgTag` в качестве третьего параметра: + +- Конструктор с тегом `ExpTag` - делегирует работу основному конструктору +- Конструктор с тегом `DegTag` - из амплитуды и фазы в градусах +- Конструктор с тегом `AlgTag` - из алгебраической формы записи (_real_, _imag_) + +Методы доступа и модификации: + +- Метод `SetPolar` - задать через полярные координаты +- Метод `SetCartesian` - задать через декартовы координаты +- Метод `Magnitude` - получить амплитуды (модуль) +- Метод `Phase` - получить фазу в радианах в диапазоне $(-\pi, \pi]$ +- Метод `PhaseDeg` - получить фазу в градусах в диапазоне $(-180, 180]$ +- Метод `Real` - получить действительную часть +- Метод `Imag` - получить мнимую часть + +Также предоставляются синонимы: + +- Метод `Abs` - синоним метода `Magnitude` +- Метод `Angle` - синоним метода `Phase` +- Метод `AngleDeg` - синоним метода `PhaseDeg` + +Арифметические операторы: + +- Оператор `+` - сложение +- Оператор `-` - вычитание +- Оператор `*` - умножение +- Оператор `/` - деление +- Унарный оператор `-` - разворачивает фазор на 180 градусов ($\pi$ радиан) + +Необходимо перегрузить соответствующие операторы составного присваивания: + +- Оператор `+=` +- Оператор `-=` +- Оператор `*=` +- Оператор `/=` + +Бинарные операторы должны работать как для двух объектов `Phasor`, так и +в операциях с `Phasor` и вещественным числом. + +Операторы сравнения: + +- Оператор `==` +- Оператор `!=` + +Методы специального назначения: + +- Метод `Conj` - возвращает комплексно-сопряженный фазор +- Метод `Inv` - возвращает обратный фазор (1/_phasor_) + +Для удобства необходимо написать функции, создающие объекты `Phasor` от +различных входных параметров: + +- Функция `MakePhasorCartesian` для создания фазора из декартовых координат +- Функция `MakePhasorPolar` для создания фазора из полярных координат +- Функция `MakePhasorPolarDeg` для создания фазора из полярных координат + с углом в градусах + +Реализовать перегрузку оператора `<<` для вывода комплексной амплитуды в +поток в формате: + +`{mag}*e(j*{arg}) [{real} + j*{imag}]`, где + +- `{mag}` - амплитуда +- `{arg}` - аргумент в градусах +- `{real}` - действительная часть +- `{imag}` - мнимая часть + +## Примечание + +- Запрещено использовать класс `std::complex` +- При реализации пригодится заголовочный файл `` +- Рекомендуется определять методы вне класса +- Амплитуда всегда имеет неотрицательное значение +- Операторы `+` и `-` удобно реализовывать в декартовых координатах +- Операторы `*` и `/` удобно реализовывать в полярных координатах diff --git a/04_week/tasks/phasor/phasor.cpp b/04_week/tasks/phasor/phasor.cpp new file mode 100644 index 00000000..459679f8 --- /dev/null +++ b/04_week/tasks/phasor/phasor.cpp @@ -0,0 +1,236 @@ +#include +#include +#include +#include + +#define UNUSED(x) (static_cast(x)) + +static inline double to_rad(double deg) { + return (deg * std::numbers::pi) / 180; +} + +static inline double to_deg(double rad) { + return (rad * 180) / std::numbers::pi; +} + +struct ExpTag {}; +struct DegTag {}; +struct AlgTag {}; + +class Phasor { +public: + Phasor(double amplitude, double phase); + Phasor(double amplitude, double phase, ExpTag tag); + Phasor(double amplitude, double phase, DegTag tag); + Phasor(double real, double imag, AlgTag tag); + + void SetPolar(double magnitude, double angle); + void SetCartesian(double x, double y); + + double Magnitude() const; + double Phase() const; + double PhaseDeg() const; + double Real() const; + double Imag() const; + + double Abs() const; + double Angle() const; + double AngleDeg() const; + + Phasor operator+(const Phasor& other) const; + Phasor operator-(const Phasor& other) const; + Phasor operator*(const Phasor& other) const; + Phasor operator/(const Phasor& other) const; + Phasor operator-() const; + + Phasor operator+(double num) const; + Phasor operator-(double num) const; + Phasor operator*(double num) const; + Phasor operator/(double num) const; + + Phasor operator+=(const Phasor& other); + Phasor operator-=(const Phasor& other); + Phasor operator*=(const Phasor& other); + Phasor operator/=(const Phasor& other); + + bool operator==(const Phasor& other) const; + bool operator!=(const Phasor& other) const; + + Phasor Conj() const; + Phasor Inv() const; + +private: + double m_real; + double m_imag; +}; + +Phasor::Phasor(double amplitude = 0.0, double phase = 0.0) { + m_real = amplitude * std::cos(phase); + m_imag = amplitude * std::sin(phase); +} + +Phasor::Phasor(double amplitude, double phase, ExpTag tag) : Phasor(amplitude, phase) { + UNUSED(tag); +} + +Phasor::Phasor(double amplitude, double phase, DegTag tag) : Phasor(amplitude, to_rad(phase)) { + UNUSED(tag); +} + +Phasor::Phasor(double real, double imag, AlgTag tag) { + UNUSED(tag); + m_real = real; + m_imag = imag; +} + +void Phasor::SetPolar(double magnitude, double angle) { + m_real = magnitude * std::cos(angle); + m_imag = magnitude * std::sin(angle); +} + +void Phasor::SetCartesian(double x, double y) { + m_real = x; + m_imag = y; +} + +double Phasor::Magnitude() const { + return std::sqrt(m_real * m_real + m_imag * m_imag); +} + +double Phasor::Phase() const { + double phase = std::atan2(m_imag, m_real); + + if (std::fabs(phase - (-std::numbers::pi)) < 1e-9) { + return std::numbers::pi; + } + + return std::atan2(m_imag, m_real); +} + +double Phasor::PhaseDeg() const { + return to_deg(Phase()); +} + +double Phasor::Real() const { + return m_real; +} + +double Phasor::Imag() const { + return m_imag; +} + +double Phasor::Abs() const { + return Magnitude(); +} + +double Phasor::Angle() const { + return Phase(); +} + +double Phasor::AngleDeg() const { + return PhaseDeg(); +} + +Phasor Phasor::operator+(const Phasor& other) const { + return Phasor(m_real + other.m_real, m_imag + other.m_imag, AlgTag{}); +} + +Phasor Phasor::operator-(const Phasor& other) const { + return Phasor(m_real - other.m_real, m_imag - other.m_imag, AlgTag{}); +} + +Phasor Phasor::operator*(const Phasor& other) const { + return Phasor(Abs() * other.Abs(), Angle() + other.Angle()); +} + +Phasor Phasor::operator/(const Phasor& other) const { + return Phasor(Abs() / other.Abs(), Angle() - other.Angle()); +} + +Phasor Phasor::operator-() const { + return Phasor(-m_real, -m_imag, AlgTag{}); +} + +Phasor Phasor::operator+(double num) const { + return *this + Phasor(num, 0.0); +} + +Phasor Phasor::operator-(double num) const { + return *this - Phasor(num, 0.0); +} + +Phasor Phasor::operator*(double num) const { + return *this * Phasor(num, 0.0); +} + +Phasor Phasor::operator/(double num) const { + return *this / Phasor(num, 0.0); +} + +Phasor operator+(double num, const Phasor& other) { + return Phasor(num, 0.0) + other; +} + +Phasor operator-(double num, const Phasor& other) { + return Phasor(num, 0.0) - other; +} + +Phasor operator*(double num, const Phasor& other) { + return Phasor(num, 0.0) * other; +} + +Phasor operator/(double num, const Phasor& other) { + return Phasor(num, 0.0) / other; +} + +Phasor Phasor::operator+=(const Phasor& other) { + return *this = this->operator+(other); +} + +Phasor Phasor::operator-=(const Phasor& other) { + return *this = this->operator-(other); +} + +Phasor Phasor::operator*=(const Phasor& other) { + return *this = this->operator*(other); +} + +Phasor Phasor::operator/=(const Phasor& other) { + return *this = this->operator/(other); +} + +bool Phasor::operator==(const Phasor& other) const { + if ((std::fabs(m_real - other.m_real) < 1e-9) && (std::fabs(m_imag - other.m_imag) < 1e-9)) { + return true; + } + + return false; +} + +bool Phasor::operator!=(const Phasor& other) const { + return !(*this == other); +} + +Phasor Phasor::Conj() const { + return Phasor(m_real, -m_imag, AlgTag{}); +} + +Phasor Phasor::Inv() const { + return 1.0 / (*this); +} + +Phasor MakePhasorCartesian(double x, double y) { + return Phasor(x, y, AlgTag{}); +} + +Phasor MakePhasorPolar(double amplitude, double phase) { + return Phasor(amplitude, phase); +} + +Phasor MakePhasorPolarDeg(double amplitude, double phase) { + return Phasor(amplitude, phase, DegTag{}); +} + +std::ostream& operator<<(std::ostream& os, const Phasor& other) { + return os << std::format("{:.3f}*e(j*{:.3f}) [{:.3f} + j*{:.3f}]", other.Abs(), other.AngleDeg(), other.Real(), other.Imag()); +} \ No newline at end of file diff --git a/04_week/tasks/phasor/test.cpp b/04_week/tasks/phasor/test.cpp new file mode 100644 index 00000000..eb92cfb9 --- /dev/null +++ b/04_week/tasks/phasor/test.cpp @@ -0,0 +1,661 @@ +#include + +#include +#include + +#include "phasor.cpp" + +namespace { + using std::numbers::pi; + constexpr double EPS = 1e-6; +} + + +TEST(PhasorTest, SizeCheck) { + EXPECT_EQ(sizeof(Phasor), 2 * sizeof(double)); +} + +TEST(PhasorTest, DefaultCtor) { + Phasor p; + EXPECT_DOUBLE_EQ(p.Magnitude(), 0.0); + EXPECT_DOUBLE_EQ(p.Phase(), 0.0); + EXPECT_DOUBLE_EQ(p.Real(), 0.0); + EXPECT_DOUBLE_EQ(p.Imag(), 0.0); +} + +TEST(PhasorTest, PolarCtor) { + { + Phasor p(10.0, pi / 4); + EXPECT_NEAR(p.Magnitude(), 10.0, EPS); + EXPECT_NEAR(p.Phase(), pi / 4, EPS); + EXPECT_NEAR(p.PhaseDeg(), 45., EPS); + EXPECT_NEAR(p.Real(), 5.0 * std::sqrt(2), EPS); + EXPECT_NEAR(p.Imag(), 5.0 * std::sqrt(2), EPS); + } + { + Phasor p(10.0, -pi / 4); + EXPECT_NEAR(p.Magnitude(), 10.0, EPS); + EXPECT_NEAR(p.Phase(), -pi / 4, EPS); + EXPECT_NEAR(p.PhaseDeg(), -45., EPS); + EXPECT_NEAR(p.Real(), 5.0 * std::sqrt(2), EPS); + EXPECT_NEAR(p.Imag(), -5.0 * std::sqrt(2), EPS); + } + { + Phasor p(10.0, 3 * pi / 4); + EXPECT_NEAR(p.Magnitude(), 10.0, EPS); + EXPECT_NEAR(p.Phase(), 3 * pi / 4, EPS); + EXPECT_NEAR(p.PhaseDeg(), 135., EPS); + EXPECT_NEAR(p.Real(), -5.0 * std::sqrt(2), EPS); + EXPECT_NEAR(p.Imag(), 5.0 * std::sqrt(2), EPS); + } + { + Phasor p(10.0, -3 * pi / 4); + EXPECT_NEAR(p.Magnitude(), 10.0, EPS); + EXPECT_NEAR(p.Phase(), -3 * pi / 4, EPS); + EXPECT_NEAR(p.PhaseDeg(), -135., EPS); + EXPECT_NEAR(p.Real(), -5.0 * std::sqrt(2), EPS); + EXPECT_NEAR(p.Imag(), -5.0 * std::sqrt(2), EPS); + } +} + +TEST(PhasorTest, NegativeMagnitude) { + { + Phasor p(-10.0, pi / 4); + EXPECT_NEAR(p.Magnitude(), 10.0, EPS); + EXPECT_NEAR(p.Phase(), pi / 4 - pi, EPS); + EXPECT_NEAR(p.PhaseDeg(), -135., EPS); + EXPECT_NEAR(p.Real(), -5.0 * std::sqrt(2), EPS); + EXPECT_NEAR(p.Imag(), -5.0 * std::sqrt(2), EPS); + } + { + Phasor p(-10.0, -pi / 4); + EXPECT_NEAR(p.Magnitude(), 10.0, EPS); + EXPECT_NEAR(p.Phase(), - pi / 4 + pi, EPS); + EXPECT_NEAR(p.PhaseDeg(), 135., EPS); + EXPECT_NEAR(p.Real(), -5.0 * std::sqrt(2), EPS); + EXPECT_NEAR(p.Imag(), 5.0 * std::sqrt(2), EPS); + } + { + Phasor p(-10.0, 3 * pi / 4); + EXPECT_NEAR(p.Magnitude(), 10.0, EPS); + EXPECT_NEAR(p.Phase(), - pi / 4, EPS); + EXPECT_NEAR(p.PhaseDeg(), -45., EPS); + EXPECT_NEAR(p.Real(), 5.0 * std::sqrt(2), EPS); + EXPECT_NEAR(p.Imag(), -5.0 * std::sqrt(2), EPS); + } + { + Phasor p(-10.0, -3 * pi / 4); + EXPECT_NEAR(p.Magnitude(), 10.0, EPS); + EXPECT_NEAR(p.Phase(), pi / 4, EPS); + EXPECT_NEAR(p.PhaseDeg(), 45., EPS); + EXPECT_NEAR(p.Real(), 5.0 * std::sqrt(2), EPS); + EXPECT_NEAR(p.Imag(), 5.0 * std::sqrt(2), EPS); + } +} + +TEST(PhasorTest, ExpTagConstructor) { + { + Phasor p(5.0, pi / 3, ExpTag{}); + EXPECT_NEAR(p.Magnitude(), 5.0, EPS); + EXPECT_NEAR(p.Phase(), pi / 3, EPS); + } + { + Phasor p(-5.0, pi / 3, ExpTag{}); + EXPECT_NEAR(p.Magnitude(), 5.0, EPS); + EXPECT_NEAR(p.Phase(), - 2 * pi / 3, EPS); + } + { + Phasor p(5.0, 2 * pi / 3, ExpTag{}); + EXPECT_NEAR(p.Magnitude(), 5.0, EPS); + EXPECT_NEAR(p.Phase(), 2 * pi / 3, EPS); + } + { + Phasor p(5.0, 7 * pi / 3, ExpTag{}); + EXPECT_NEAR(p.Magnitude(), 5.0, EPS); + EXPECT_NEAR(p.Phase(), pi / 3, EPS); + } +} + +TEST(PhasorTest, DegTagConstructor) { + { + Phasor p(5.0, 60.0, DegTag{}); + EXPECT_NEAR(p.Magnitude(), 5.0, EPS); + EXPECT_NEAR(p.PhaseDeg(), 60.0, EPS); + EXPECT_NEAR(p.Phase(), pi / 3, EPS); + } + { + Phasor p(5.0, -60.0, DegTag{}); + EXPECT_NEAR(p.Magnitude(), 5.0, EPS); + EXPECT_NEAR(p.PhaseDeg(), -60.0, EPS); + EXPECT_NEAR(p.Phase(), -pi / 3, EPS); + } + { + Phasor p(-5.0, -60.0, DegTag{}); + EXPECT_NEAR(p.Magnitude(), 5.0, EPS); + EXPECT_NEAR(p.PhaseDeg(), 120.0, EPS); + EXPECT_NEAR(p.Phase(), 2 * pi / 3, EPS); + } + { + Phasor p(5.0, -360.0, DegTag{}); + EXPECT_NEAR(p.Magnitude(), 5.0, EPS); + EXPECT_NEAR(p.PhaseDeg(), 0.0, EPS); + EXPECT_NEAR(p.Phase(), 0.0, EPS); + } +} + +TEST(PhasorTest, AlgTagConstructor) { + { + Phasor p(3.0, 4.0, AlgTag{}); + EXPECT_NEAR(p.Magnitude(), 5.0, EPS); + EXPECT_NEAR(p.Phase(), std::atan2(4.0, 3.0), EPS); + EXPECT_DOUBLE_EQ(p.Real(), 3.0); + EXPECT_DOUBLE_EQ(p.Imag(), 4.0); + } + { + Phasor p(std::sqrt(2), std::sqrt(2), AlgTag{}); + EXPECT_NEAR(p.Magnitude(), 2.0, EPS); + EXPECT_NEAR(p.Phase(), pi / 4, EPS); + EXPECT_DOUBLE_EQ(p.Real(), std::sqrt(2)); + EXPECT_DOUBLE_EQ(p.Imag(), std::sqrt(2)); + } + { + Phasor p(-std::sqrt(2), -std::sqrt(2), AlgTag{}); + EXPECT_NEAR(p.Magnitude(), 2.0, EPS); + EXPECT_NEAR(p.Phase(), -3 * pi / 4, EPS); + EXPECT_DOUBLE_EQ(p.Real(), -std::sqrt(2)); + EXPECT_DOUBLE_EQ(p.Imag(), -std::sqrt(2)); + } + { + Phasor p(std::sqrt(2), -std::sqrt(2), AlgTag{}); + EXPECT_NEAR(p.Magnitude(), 2.0, EPS); + EXPECT_NEAR(p.Phase(), -pi / 4, EPS); + EXPECT_DOUBLE_EQ(p.Real(), std::sqrt(2)); + EXPECT_DOUBLE_EQ(p.Imag(), -std::sqrt(2)); + } + { + Phasor p(-std::sqrt(2), std::sqrt(2), AlgTag{}); + EXPECT_NEAR(p.Magnitude(), 2.0, EPS); + EXPECT_NEAR(p.Phase(), 3 * pi / 4, EPS); + EXPECT_DOUBLE_EQ(p.Real(), -std::sqrt(2)); + EXPECT_DOUBLE_EQ(p.Imag(), std::sqrt(2)); + } +} + +TEST(PhasorTest, PhaseNormalization) { + Phasor p1(1.0, 3 * pi / 2); + EXPECT_NEAR(p1.Phase(), -pi / 2, EPS); + EXPECT_NEAR(p1.PhaseDeg(), -90., EPS); + + Phasor p2(1.0, -3 * pi / 2); + EXPECT_NEAR(p2.Phase(), pi / 2, EPS); + EXPECT_NEAR(p2.PhaseDeg(), 90., EPS); + + Phasor p3(1.0, 2 * pi + pi / 4); + EXPECT_NEAR(p3.Phase(), pi / 4, EPS); + EXPECT_NEAR(p3.PhaseDeg(), 45., EPS); +} + +TEST(PhasorTest, Synonyms) { + Phasor p(3.0, 4.0,AlgTag{}); + EXPECT_DOUBLE_EQ(p.Magnitude(), p.Abs()); + EXPECT_DOUBLE_EQ(p.Phase(), p.Angle()); + EXPECT_DOUBLE_EQ(p.PhaseDeg(), p.AngleDeg()); +} + +TEST(PhasorTest, SetPolar) { + Phasor p; + p.SetPolar(10.0, pi / 6); + + EXPECT_NEAR(p.Magnitude(), 10.0, EPS); + EXPECT_NEAR(p.Phase(), pi / 6, EPS); + EXPECT_NEAR(p.Real(), 10.0 * std::sqrt(3) / 2, EPS); + EXPECT_NEAR(p.Imag(), 5.0, EPS); +} + +TEST(PhasorTest, SetCartesian) { + Phasor p; + p.SetCartesian(1.0, sqrt(3.0)); + + EXPECT_NEAR(p.Magnitude(), 2.0, EPS); + EXPECT_NEAR(p.Phase(), pi / 3, EPS); + EXPECT_DOUBLE_EQ(p.Real(), 1.0); + EXPECT_DOUBLE_EQ(p.Imag(), std::sqrt(3.0)); +} + +TEST(MathPhasorTest, UnaryMinus) { + Phasor p(3.0, 4.0, AlgTag{}); + Phasor p2 = -p; + + EXPECT_DOUBLE_EQ(p2.Real(), -3.0); + EXPECT_DOUBLE_EQ(p2.Imag(), -4.0); + EXPECT_NEAR(p2.Magnitude(), p.Magnitude(), EPS); + EXPECT_NEAR(p2.Phase(), p.Phase() - pi, EPS); +} + +TEST(MathPhasorTest, Addition) { + Phasor p1(1.0, 2.0, AlgTag{}); + Phasor p2(3.0, 4.0, AlgTag{}); + Phasor res = p1 + p2; + + Phasor expected(4.0, 6.0, AlgTag{}); + EXPECT_EQ(res, expected); +} + +TEST(MathPhasorTest, Subtraction) { + Phasor p1(5.0, 6.0, AlgTag{}); + Phasor p2(2.0, -3.0, AlgTag{}); + Phasor res = p1 - p2; + + Phasor expected(3.0, 9.0, AlgTag{}); + EXPECT_EQ(res, expected); +} + +TEST(MathPhasorTest, Multiplication) { + Phasor p1(1.0, 2.0, AlgTag{}); + Phasor p2(3.0, 4.0, AlgTag{}); + Phasor res = p1 * p2; + + EXPECT_NEAR(res.Real(), -5.0, EPS); + EXPECT_NEAR(res.Imag(), 10.0, EPS); + + Phasor p3(2.0, 60.0, DegTag{}); + Phasor p4(3.0, 30.0, DegTag{}); + res = p3 * p4; + + EXPECT_NEAR(res.Magnitude(), 6.0, EPS); + EXPECT_NEAR(res.PhaseDeg(), 90.0, EPS); +} + +TEST(MathPhasorTest, Division) { + Phasor p1(3.0, 4.0, AlgTag{}); + Phasor p2(1.0, 1.0, AlgTag{}); + Phasor res = p1 / p2; + + EXPECT_NEAR(res.Real(), 3.5, EPS); + EXPECT_NEAR(res.Imag(), 0.5, EPS); + + Phasor p3(6.0, 90.0, DegTag{}); + Phasor p4(3.0, 30.0, DegTag{}); + res = p3 / p4; + + EXPECT_NEAR(res.Magnitude(), 2.0, EPS); + EXPECT_NEAR(res.PhaseDeg(), 60.0, EPS); +} + +TEST(MathPhasorTest, CompoundAssignment) { + Phasor p1(2.0, 3.0, AlgTag{}); + Phasor p2(1.0, 1.0, AlgTag{}); + + p1 += p2; + EXPECT_DOUBLE_EQ(p1.Real(), 3.0); + EXPECT_DOUBLE_EQ(p1.Imag(), 4.0); + + p1 -= p2; + EXPECT_DOUBLE_EQ(p1.Real(), 2.0); + EXPECT_DOUBLE_EQ(p1.Imag(), 3.0); + + p1 *= Phasor(2.0, 0.0, DegTag{}); + EXPECT_NEAR(p1.Magnitude(), 2.0 * sqrt(13.0), EPS); + EXPECT_DOUBLE_EQ(p1.Phase(), atan2(3.0, 2.0)); + + p1 /= Phasor(2.0, 0.0, DegTag{}); + EXPECT_NEAR(p1.Magnitude(), std::sqrt(13.0), EPS); + EXPECT_DOUBLE_EQ(p1.Phase(), std::atan2(3.0, 2.0)); +} + +TEST(MathPhasorTest, ScalarAddition) { + Phasor p(1.0, 2.0, AlgTag{}); + Phasor res = p + 3.0; + EXPECT_DOUBLE_EQ(res.Real(), 4.0); + EXPECT_DOUBLE_EQ(res.Imag(), 2.0); + + res = -3.0 + p; + EXPECT_DOUBLE_EQ(res.Real(), -2.0); + EXPECT_DOUBLE_EQ(res.Imag(), 2.0); +} + +TEST(MathPhasorTest, ScalarSubtraction) { + Phasor p(5.0, 2.0, AlgTag{}); + Phasor res = p - 7.0; + EXPECT_DOUBLE_EQ(res.Real(), -2.0); + EXPECT_DOUBLE_EQ(res.Imag(), 2.0); + + res = 3.0 - p; + EXPECT_DOUBLE_EQ(res.Real(), -2.0); + EXPECT_DOUBLE_EQ(res.Imag(), -2.0); +} + +TEST(MathPhasorTest, ScalarMultiplication) { + Phasor p(2.0, 60.0, DegTag{}); + + Phasor res = p * 3.0; + EXPECT_NEAR(res.Magnitude(), 6.0, EPS); + EXPECT_NEAR(res.PhaseDeg(), 60.0, EPS); + + res = -2.0 * p; + EXPECT_NEAR(res.Magnitude(), 4.0, EPS); + EXPECT_NEAR(res.PhaseDeg(), 60.0 - 180.0, EPS); + + res = 3.0 * p; + EXPECT_NEAR(res.Magnitude(), 6.0, EPS); + EXPECT_NEAR(res.PhaseDeg(), 60.0, EPS); +} + +TEST(MathPhasorTest, ScalarDivision) { + Phasor p(6.0, 60.0, DegTag{}); + + Phasor res = p / 2.0; + EXPECT_NEAR(res.Magnitude(), 3.0, EPS); + EXPECT_NEAR(res.PhaseDeg(), 60.0, EPS); + + res = 12.0 / p; + EXPECT_NEAR(res.Magnitude(), 2.0, EPS); + EXPECT_NEAR(res.PhaseDeg(), -60.0, EPS); +} + +TEST(PhasorTest, Conjugate) { + Phasor p(3.0, 4.0,AlgTag{}); + Phasor conj = p.Conj(); + + EXPECT_DOUBLE_EQ(conj.Real(), p.Real()); + EXPECT_DOUBLE_EQ(conj.Imag(), -p.Imag()); + EXPECT_DOUBLE_EQ(conj.Magnitude(), p.Magnitude()); + EXPECT_DOUBLE_EQ(conj.Phase(), -p.Phase()); + + Phasor res = p * conj; + EXPECT_NEAR(res.Imag(), 0.0, EPS); + EXPECT_NEAR(res.Real(), p.Magnitude() * p.Magnitude(), EPS); +} + +TEST(PhasorTest, Inverse) { + Phasor p(4.0, 60.0, DegTag{}); + Phasor inv = p.Inv(); + + EXPECT_NEAR(inv.Magnitude(), 0.25, EPS); + EXPECT_NEAR(inv.PhaseDeg(), -60.0, EPS); + + Phasor res = p * inv; + EXPECT_NEAR(res.Magnitude(), 1.0, EPS); + EXPECT_NEAR(res.Phase(), 0.0, EPS); +} + +TEST(PhasorTest, Equality) { + Phasor p1(1.0, 2.0, AlgTag{}); + Phasor p2(1.0, 2.0, AlgTag{}); + Phasor p3(1.0, 2.000000001, AlgTag{}); + + EXPECT_TRUE(p1 == p2); + EXPECT_FALSE(p1 != p2); + EXPECT_FALSE(p1 == p3); + EXPECT_TRUE(p1 != p3); + + Phasor p4(1.0, 45.0, DegTag{}); + Phasor p5(std::cos(pi / 4), std::sin(pi / 4), AlgTag{}); + EXPECT_TRUE(p4 == p5); +} + +TEST(PhasorTest, FactoryFunctions) { + Phasor p1 = MakePhasorCartesian(3.0, 4.0); + EXPECT_DOUBLE_EQ(p1.Real(), 3.0); + EXPECT_DOUBLE_EQ(p1.Imag(), 4.0); + + Phasor p2 = MakePhasorPolar(5.0, pi / 3); + EXPECT_NEAR(p2.Magnitude(), 5.0, EPS); + EXPECT_NEAR(p2.Phase(), pi / 3, EPS); + + Phasor p3 = MakePhasorPolarDeg(5.0, 60.0); + EXPECT_NEAR(p3.Magnitude(), 5.0, EPS); + EXPECT_NEAR(p3.PhaseDeg(), 60.0, EPS); +} + +TEST(PhasorTest, OutputOperator) { + Phasor p(5.0, 45.0, DegTag{}); + std::stringstream ss; + ss << p; + + std::string output = ss.str(); + + EXPECT_TRUE(output.find("5.000") != std::string::npos); + EXPECT_TRUE(output.find("45.000") != std::string::npos); + EXPECT_TRUE(output.find("*e(j*") != std::string::npos); + EXPECT_TRUE(output.find(") [") != std::string::npos); + EXPECT_TRUE(output.find(" + j*") != std::string::npos); + EXPECT_TRUE(output.find("]") != std::string::npos); +} + +TEST(MathPhasorTest, ComplexCalculations) { + Phasor z1(3.0, 30.0, DegTag{}); + Phasor z2(4.0, 60.0, DegTag{}); + Phasor z3(2.0, -45.0, DegTag{}); + + Phasor result = (z1 + z2) * z3 / 2.0; + + EXPECT_NEAR(result.Magnitude(), 6.7664, 0.0001); + EXPECT_NEAR(result.PhaseDeg(), 2.1921, 0.0001); + EXPECT_NEAR(result.Magnitude(), 6.7664, 0.0001); + EXPECT_NEAR(result.Real(), 6.7615, 0.0001); + EXPECT_NEAR(result.Imag(), 0.2588, 0.0001); +} + +TEST(MathPhasorTest, CommutativeProperty) { + Phasor p1(2.0, 3.0, AlgTag{}); + Phasor p2(4.0, 5.0,AlgTag{}); + + EXPECT_TRUE(p1 + p2 == p2 + p1); + EXPECT_TRUE(p1 * p2 == p2 * p1); +} + +TEST(MathPhasorTest, AssociativeProperty) { + Phasor p1(1.0, 2.0, AlgTag{}); + Phasor p2(3.0, 4.0, AlgTag{}); + Phasor p3(5.0, 6.0, AlgTag{}); + + Phasor left = (p1 + p2) + p3; + Phasor right = p1 + (p2 + p3); + EXPECT_TRUE(left == right); + + left = (p1 * p2) * p3; + right = p1 * (p2 * p3); + EXPECT_TRUE(left == right); +} + +TEST(MathPhasorTest, DistributiveProperty) { + Phasor p1(1.0, 2.0, AlgTag{}); + Phasor p2(3.0, 4.0, AlgTag{}); + Phasor p3(5.0, 6.0, AlgTag{}); + + Phasor left = p1 * (p2 + p3); + Phasor right = p1 * p2 + p1 * p3; + EXPECT_TRUE(left == right); +} + +TEST(PhasorTest, SpecialAngles) { + Phasor p1(1.0, 0.0, DegTag{}); + EXPECT_NEAR(p1.Real(), 1.0, EPS); + EXPECT_NEAR(p1.Imag(), 0.0, EPS); + + Phasor p2(1.0, 90.0, DegTag{}); + EXPECT_NEAR(p2.Real(), 0.0, EPS); + EXPECT_NEAR(p2.Imag(), 1.0, EPS); + + Phasor p3(1.0, 180.0, DegTag{}); + EXPECT_NEAR(p3.Real(), -1.0, EPS); + EXPECT_NEAR(p3.Imag(), 0.0, EPS); + EXPECT_NEAR(p3.PhaseDeg(), 180.0, EPS); + + p3 = Phasor(1.0, -180.0, DegTag{}); + EXPECT_NEAR(p3.Real(), -1.0, EPS); + EXPECT_NEAR(p3.Imag(), 0.0, EPS); + EXPECT_NEAR(p3.PhaseDeg(), 180.0, EPS); + + Phasor p4(1.0, -90.0, DegTag{}); + EXPECT_NEAR(p4.Real(), 0.0, EPS); + EXPECT_NEAR(p4.Imag(), -1.0, EPS); +} + +TEST(ConstPhasorTest, ConstPhasorMethods) { + const Phasor p(5.0, 60.0, DegTag{}); + + EXPECT_NEAR(p.Magnitude(), 5.0, EPS); + EXPECT_NEAR(p.Phase(), pi / 3, EPS); + EXPECT_NEAR(p.PhaseDeg(), 60.0, EPS); + EXPECT_NEAR(p.Real(), 2.5, EPS); + EXPECT_NEAR(p.Imag(), 5.0 * std::sqrt(3) / 2, EPS); + + EXPECT_NEAR(p.Abs(), 5.0, EPS); + EXPECT_NEAR(p.Angle(), p.Phase(), EPS); + EXPECT_NEAR(p.AngleDeg(), 60.0, EPS); +} + +TEST(ConstPhasorTest, ConstPhasorComparison) { + const Phasor p1(3.0, 45.0, DegTag{}); + const Phasor p2(3.0, 45.0, DegTag{}); + const Phasor p3(4.0, 45.0, DegTag{}); + + EXPECT_TRUE(p1 == p2); + EXPECT_FALSE(p1 != p2); + EXPECT_FALSE(p1 == p3); + EXPECT_TRUE(p1 != p3); +} + +TEST(ConstPhasorTest, ConstPhasorUnaryOperators) { + const Phasor p(3.0, 30.0, DegTag{}); + + Phasor p2 = -p; + EXPECT_NEAR(p2.Magnitude(), p.Magnitude(), EPS); + EXPECT_NEAR(p2.Phase(), p.Phase() - pi, EPS); +} + +TEST(ConstPhasorTest, ConstPhasorBinaryOperators) { + const Phasor p1(2.0, 30.0, DegTag{}); + const Phasor p2(3.0, 60.0, DegTag{}); + + Phasor res = p1 + p2; + EXPECT_NEAR(res.Magnitude(), 4.8366, 0.0001); + EXPECT_NEAR(res.Phase(), 0.8389, 0.0001); + EXPECT_NEAR(res.PhaseDeg(), 48.0675, 0.0001); + EXPECT_NEAR(res.Real(), 3.2321, 0.0001); + EXPECT_NEAR(res.Imag(), 3.5981, 0.0001); + + res = p1 - p2; + EXPECT_NEAR(res.Magnitude(), 1.6148, 0.0001); + EXPECT_NEAR(res.Phase(), -1.4266, 0.0001); + EXPECT_NEAR(res.PhaseDeg(), -81.7380, 0.0001); + EXPECT_NEAR(res.Real(), 0.2321, 0.0001); + EXPECT_NEAR(res.Imag(), -1.5981, 0.0001); + + res = p1 * p2; + EXPECT_NEAR(res.Magnitude(), 6.0, 0.0001); + EXPECT_NEAR(res.Phase(), 1.5708, 0.0001); + EXPECT_NEAR(res.PhaseDeg(), 90.0000, 0.0001); + EXPECT_NEAR(res.Real(), 0.0000, 0.0001); + EXPECT_NEAR(res.Imag(), 6.0000, 0.0001); + + res = p1 / p2; + EXPECT_NEAR(res.Magnitude(), 0.6667, 0.0001); + EXPECT_NEAR(res.Phase(), -0.5236, 0.0001); + EXPECT_NEAR(res.PhaseDeg(), -30.0000, 0.0001); + EXPECT_NEAR(res.Real(), 0.5774, 0.0001); + EXPECT_NEAR(res.Imag(), -0.3333, 0.0001); + + Phasor p3 = p1 + 2.0; + Phasor p4 = 2.0 + p1; + Phasor p5 = p1 * 2.0; + Phasor p6 = 2.0 * p1; + + EXPECT_TRUE(p3 == p4); + EXPECT_TRUE(p5 == p6); +} + +TEST(ConstPhasorTest, ConstPhasorSpecialMethods) { + const Phasor p(4.0, 45.0, DegTag{}); + + Phasor conj = p.Conj(); + EXPECT_NEAR(conj.Real(), p.Real(), EPS); + EXPECT_NEAR(conj.Imag(), -p.Imag(), EPS); + + Phasor inv = p.Inv(); + EXPECT_NEAR(inv.Magnitude(), 1.0/p.Magnitude(), EPS); + EXPECT_NEAR(inv.Phase(), -p.Phase(), EPS); +} + +TEST(ConstPhasorTest, ConstPhasorInExpression) { + const Phasor a(3.0, 0.0, DegTag{}); + const Phasor b(4.0, 90.0, DegTag{}); + + Phasor result = (a * a.Conj() + b * b.Conj()) / 2.0; + + EXPECT_NEAR(result.Magnitude(), 12.5, EPS); + EXPECT_NEAR(result.Phase(), 0.0, EPS); +} + +TEST(ConstPhasorTest, ConstPhasorVectorOperations) { + const Phasor phasors[] = { + Phasor(1.0, 0.0, DegTag{}), + Phasor(1.0, 90.0, DegTag{}), + Phasor(1.0, 180.0, DegTag{}), + Phasor(1.0, 270.0, DegTag{}) + }; + Phasor sum; + for (const auto& p : phasors) { + sum += p; + } + EXPECT_NEAR(sum.Magnitude(), 0.0, EPS); +} + +TEST(ConstPhasorTest, ConstPhasorOutput) { + const Phasor p(2.5, 30.0, DegTag{}); + std::stringstream ss; + + ss << p; + + std::string output = ss.str(); + EXPECT_TRUE(output.find("2.500") != std::string::npos); + EXPECT_TRUE(output.find("30.000") != std::string::npos); +} + +TEST(ConstPhasorTest, ConstPhasorAssignmentFromConst) { + const Phasor source(10.0, 45.0, DegTag{}); + Phasor target; + + target = source; + + EXPECT_TRUE(target == source); + EXPECT_NEAR(target.Magnitude(), 10.0, EPS); + EXPECT_NEAR(target.PhaseDeg(), 45.0, EPS); +} + +TEST(ConstPhasorTest, MixedConstAndNonConst) { + const Phasor cp(3.0, 60.0, DegTag{}); + Phasor ncp(4.0, 30.0, DegTag{}); + + Phasor sum1 = cp + ncp; + Phasor sum2 = ncp + cp; + + EXPECT_TRUE(sum1 == sum2); + + Phasor prod1 = cp * ncp; + Phasor prod2 = ncp * cp; + + EXPECT_TRUE(prod1 == prod2); + + ncp += cp; + ncp -= cp; + ncp *= cp; + ncp /= cp; + + EXPECT_NEAR(ncp.Magnitude(), 4.0, EPS); + EXPECT_NEAR(ncp.PhaseDeg(), 30.0, EPS); +} + +TEST(ConstPhasorTest, ConstPhasorChainOperations) { + const Phasor a(2.0, 0.0, DegTag{}); + const Phasor b(3.0, 90.0, DegTag{}); + const Phasor c(4.0, 180.0, DegTag{}); + + Phasor result = a.Conj() * b + c / a - b.Inv(); + EXPECT_TRUE(result.Magnitude() >= 0.0); + + result = (a + b) * c.Conj() / 2.0; + EXPECT_TRUE(result.Magnitude() >= 0.0); +} diff --git a/04_week/tasks/queue/CMakeLists.txt b/04_week/tasks/queue/CMakeLists.txt new file mode 100644 index 00000000..0c8eae8a --- /dev/null +++ b/04_week/tasks/queue/CMakeLists.txt @@ -0,0 +1 @@ +add_gtest(test_queue test.cpp) \ No newline at end of file diff --git a/04_week/tasks/queue/README.md b/04_week/tasks/queue/README.md new file mode 100644 index 00000000..9fffd157 --- /dev/null +++ b/04_week/tasks/queue/README.md @@ -0,0 +1,46 @@ +[Очередь]: https://en.wikipedia.org/wiki/Queue_(abstract_data_type) + +# Очередь + +[Очередь] представляет собой FIFO (First Input First Output) структуру данных. +Необходимо реализовать класс `Queue`, который представляет собой очередь, +реализованную на **двух** контейнерах `std::vector`, используемых в качестве +стека. + +Элементы добавляются в конец одного контейнера (назовем его входным). +Достаются элементы из конца второго контейнера (назовем его выходным). +Если элементов в выходном контейнере нет, то перекладываются все элементы из +входного контейнера с конца в конец выходного контейнера (словно перекладываются +элементы из одного стека в другой). При таком перекладывании первый элемент +входного контейнера находится на верхушке выходного и его легко убрать за `O(1)`. +Все методы при такой реализации будут работать за амортизированную `O(1)`. + +Класс предоставляет следующий функционал: + +- Конструктор от `std::stack` +- Конструктор от `std::vector` +- Конструктор от `std::initializer_list` +- Конструктор от размера очереди выделяет достаточное количество памяти для работы + с очередью заданного размера без реалокации памяти. +- Метод `Push` - добавляет элемент в конец очереди +- Метод `Pop` - убирает элемент из начала очереди +- Метод `Front` - обеспечивает доступ на запись и чтение к элементу в начале очереди +- Метод `Back` - обеспечивает доступ на запись и чтение к элементу в конце очереди +- Метод `Empty` - возвращает результат проверки очереди на отсутствие элементов +- Метод `Size` - возвращает количество элементов в очереди +- Метод `Clear` - очищает очередь +- Метод `Swap` - меняется элементами с другой очередью (без копирования) +- Оператор `==` - сравнение очереди на равенство +- Оператор `!=` - сравнение очереди на неравенство + +Вызов метода `Pop` от пустой очереди является корректной операцией. Метод должен +возвращать `true` или `false` в зависимости от того выполнилась операция или нет. + +Вызов `Front` и `Back` от пустой очереди не ограничивается и ведет к **UB**. + +## Примечание + +- Запрещено использовать `std::queue`, `std::deque`, `std::list` +- Рекомендуется определять методы вне класса +- Некоторые методы могут потребовать перегрузки +- При необходимости вспомогательные методы реализуются в закрытой части класса \ No newline at end of file diff --git a/04_week/tasks/queue/queue.cpp b/04_week/tasks/queue/queue.cpp new file mode 100644 index 00000000..74ca4c58 --- /dev/null +++ b/04_week/tasks/queue/queue.cpp @@ -0,0 +1,139 @@ +#include +#include +#include +#include +#include + +class Queue { +public: + Queue() {} + Queue(size_t size); + Queue(std::stack st); + Queue(const std::vector& vec); + Queue(std::initializer_list il); + + void Push(int element); + bool Pop(); + int& Front(); + const int& Front() const; + int& Back(); + const int& Back() const; + bool Empty() const; + size_t Size() const; + void Clear(); + void Swap(Queue& other); + + bool operator==(const Queue& other) const; + bool operator!=(const Queue& other) const; + +private: + std::vector m_input; + std::vector m_output; +}; + +Queue::Queue(size_t size) { + if (size > 0) { + m_input.reserve(size); + m_output.reserve(size); + } +} + +Queue::Queue(std::stack st) : Queue(st.size()) { + while (!st.empty()) { + m_output.push_back(st.top()); + st.pop(); + } +} + +Queue::Queue(const std::vector& vec) : Queue(vec.size()) { + std::reverse_copy(vec.cbegin(), vec.cend(), std::back_inserter(m_output)); +} + +Queue::Queue(std::initializer_list il) : Queue(il.size()) { + std::reverse_copy(il.begin(), il.end(), std::back_inserter(m_output)); +} + +void Queue::Push(int element) { + m_input.push_back(element); +} + +bool Queue::Pop() { + if (Empty()) { + return false; + } + + if (m_output.empty()) { + std::reverse_copy(m_input.cbegin(), m_input.cend(), std::back_inserter(m_output)); + m_input.clear(); + } + + m_output.pop_back(); + + return true; +} + +int& Queue::Front() { + if (m_output.empty()) { + return m_input.front(); + } + + return m_output.back(); +} + +const int& Queue::Front() const { + return (const_cast(this))->Front(); +} + +int& Queue::Back() { + if (m_input.empty()) { + return m_output.front(); + } + + return m_input.back(); +} + +const int& Queue::Back() const { + return (const_cast(this))->Back(); +} + +bool Queue::Empty() const { + return m_input.empty() && m_output.empty(); +} + +size_t Queue::Size() const { + return m_input.size() + m_output.size(); +} + +void Queue::Clear() { + m_input.clear(); + m_output.clear(); +} + +void Queue::Swap(Queue& other) { + m_input.swap(other.m_input); + m_output.swap(other.m_output); +} + +bool Queue::operator==(const Queue& other) const { + if (Size() != other.Size()) { + return false; + } + + std::vector value1; + std::vector value2; + + value1.reserve(Size()); + value2.reserve(Size()); + + std::reverse_copy(m_output.cbegin(), m_output.cend(), std::back_inserter(value1)); + std::copy(m_input.cbegin(), m_input.cend(), std::back_inserter(value1)); + + std::reverse_copy(other.m_output.cbegin(), other.m_output.cend(), std::back_inserter(value2)); + std::copy(other.m_input.cbegin(), other.m_input.cend(), std::back_inserter(value2)); + + return value1 == value2; +} + +bool Queue::operator!=(const Queue& other) const { + return !(*this == other); +} \ No newline at end of file diff --git a/04_week/tasks/queue/test.cpp b/04_week/tasks/queue/test.cpp new file mode 100644 index 00000000..3bc5e562 --- /dev/null +++ b/04_week/tasks/queue/test.cpp @@ -0,0 +1,446 @@ +#include + +#include +#include + +#include "queue.cpp" + + +TEST(QueueTest, DefaultCtor) { + Queue q; + EXPECT_TRUE(q.Empty()); + EXPECT_EQ(q.Size(), 0); +} + +TEST(QueueTest, VectorCtor) { + std::vector vec = {1, 2, 3, 4, 5}; + Queue q(vec); + + EXPECT_EQ(q.Size(), 5); + EXPECT_EQ(q.Front(), 1); + EXPECT_EQ(q.Back(), 5); + for (size_t i = 0; i < vec.size(); ++i) { + EXPECT_EQ(q.Front(), vec[i]); + EXPECT_TRUE(q.Pop()); + } + EXPECT_TRUE(q.Empty()); + EXPECT_EQ(q.Size(), 0); +} + +TEST(QueueTest, StackCtor) { + std::vector vec = {1, 2, 3, 4, 5}; + std::stack s; + for (size_t i = 0; i < vec.size(); ++i) { + s.push(vec[i]); + } + Queue q(s); + + EXPECT_EQ(q.Size(), 5); + EXPECT_EQ(q.Front(), 1); + EXPECT_EQ(q.Back(), 5); + for (size_t i = 0; i < vec.size(); ++i) { + EXPECT_EQ(q.Front(), vec[i]); + EXPECT_TRUE(q.Pop()); + } + EXPECT_TRUE(q.Empty()); + EXPECT_EQ(q.Size(), 0); +} + +TEST(QueueTest, InitializerListCtor) { + Queue q = {10, 20, 30, 40, 50}; + + EXPECT_EQ(q.Size(), 5); + EXPECT_EQ(q.Front(), 10); + EXPECT_EQ(q.Back(), 50); + for (size_t i = 0; i < 5; ++i) { + EXPECT_EQ(q.Front(), (i + 1) * 10); + EXPECT_TRUE(q.Pop()); + } + EXPECT_TRUE(q.Empty()); + EXPECT_EQ(q.Size(), 0); +} + +TEST(QueueTest, PushAndSize) { + Queue q; + + q.Push(10); + EXPECT_EQ(q.Size(), 1); + EXPECT_FALSE(q.Empty()); + + q.Push(20); + EXPECT_EQ(q.Size(), 2); + + q.Push(30); + EXPECT_EQ(q.Size(), 3); +} + +TEST(QueueTest, Pop) { + Queue q = {1, 2, 3, 4, 5}; + + EXPECT_TRUE(q.Pop()); + EXPECT_EQ(q.Size(), 4); + EXPECT_EQ(q.Front(), 2); + + EXPECT_TRUE(q.Pop()); + EXPECT_EQ(q.Size(), 3); + EXPECT_EQ(q.Front(), 3); + + while (!q.Empty()) { + EXPECT_TRUE(q.Pop()); + } + EXPECT_FALSE(q.Pop()); + + EXPECT_TRUE(q.Empty()); + EXPECT_EQ(q.Size(), 0); +} + +TEST(QueueTest, Front) { + Queue q = {10, 20, 30}; + + EXPECT_EQ(q.Front(), 10); + q.Front() = 100; + EXPECT_EQ(q.Front(), 100); + q.Pop(); + + EXPECT_EQ(q.Front(), 20); + q.Front() = 200; + EXPECT_EQ(q.Front(), 200); + q.Pop(); + + EXPECT_EQ(q.Front(), 30); + q.Front() = 300; + EXPECT_EQ(q.Front(), 300); +} + +TEST(QueueTest, Back) { + Queue q = {10, 20, 30}; + + EXPECT_EQ(q.Back(), 30); + q.Back() = 300; + EXPECT_EQ(q.Back(), 300); + + q.Push(40); + EXPECT_EQ(q.Back(), 40); + q.Back() = 400; + EXPECT_EQ(q.Back(), 400); + + q.Pop(); + EXPECT_EQ(q.Back(), 400); +} + +TEST(QueueTest, FrontBack) { + Queue q; + + q.Push(1); + EXPECT_EQ(q.Front(), 1); + EXPECT_EQ(q.Back(), 1); + + q.Push(2); + EXPECT_EQ(q.Front(), 1); + EXPECT_EQ(q.Back(), 2); + + q.Push(3); + EXPECT_EQ(q.Front(), 1); + EXPECT_EQ(q.Back(), 3); + + q.Pop(); + EXPECT_EQ(q.Front(), 2); + EXPECT_EQ(q.Back(), 3); + + q.Pop(); + EXPECT_EQ(q.Front(), 3); + EXPECT_EQ(q.Back(), 3); +} + +TEST(QueueTest, FrontOnEmptyQueue) { + Queue q; + EXPECT_NO_THROW(q.Front()); + + const Queue& cq = q; + EXPECT_NO_THROW(cq.Front()); +} + +TEST(QueueTest, BackOnEmptyQueue) { + Queue q; + EXPECT_NO_THROW(q.Back()); + + const Queue& cq = q; + EXPECT_NO_THROW(cq.Back()); +} + +TEST(QueueTest, Clear) { + Queue q = {1, 2, 3, 4, 5}; + + EXPECT_EQ(q.Size(), 5); + EXPECT_FALSE(q.Empty()); + + q.Clear(); + EXPECT_EQ(q.Size(), 0); + EXPECT_TRUE(q.Empty()); + + q.Push(100); + EXPECT_EQ(q.Size(), 1); + EXPECT_FALSE(q.Empty()); + EXPECT_EQ(q.Front(), 100); + EXPECT_EQ(q.Back(), 100); +} + +TEST(QueueTest, Swap) { + Queue q1 = {1, 2, 3}; + Queue q2 = {4, 5, 6, 7}; + + q1.Swap(q2); + + EXPECT_EQ(q1.Size(), 4); + EXPECT_EQ(q1.Front(), 4); + EXPECT_EQ(q1.Back(), 7); + + EXPECT_EQ(q2.Size(), 3); + EXPECT_EQ(q2.Front(), 1); + EXPECT_EQ(q2.Back(), 3); + + q1.Swap(q1); + EXPECT_EQ(q1.Size(), 4); + EXPECT_EQ(q1.Front(), 4); + EXPECT_EQ(q1.Back(), 7); +} + +TEST(QueueTest, Equality) { + Queue q1 = {1, 2, 3, 4, 5}; + Queue q2 = {1, 2, 3, 4, 5}; + Queue q3 = {1, 2, 3, 4}; + Queue q4 = {5, 4, 3, 2, 1}; + Queue q5 = {2, 3, 4, 5}; + + EXPECT_TRUE(q1 == q1); + EXPECT_FALSE(q1 != q1); + EXPECT_TRUE(q1 == q2); + EXPECT_FALSE(q1 != q2); + + EXPECT_FALSE(q1 == q3); + EXPECT_TRUE(q1 != q3); + + EXPECT_FALSE(q1 == q4); + EXPECT_TRUE(q1 != q4); + + EXPECT_FALSE(q1 == q5); + EXPECT_TRUE(q1 != q5); + q1.Pop(); + EXPECT_TRUE(q1 == q5); + EXPECT_FALSE(q1 != q5); +} + +TEST(QueueTest, EqualityAfterOperations) { + Queue q1; + Queue q2; + + q1.Push(1); q1.Push(2); q1.Push(3); q1.Pop(); q1.Push(4); + q2.Push(2); q2.Push(3); q2.Push(4); + + EXPECT_TRUE(q1 == q2); +} + +TEST(QueueTest, EqualityAfterManyOperations) { + Queue q3; + Queue q4; + + for (int i = 0; i < 1000; ++i) { + q3.Push(i); + q4.Push(i); + } + + for (int i = 0; i < 500; ++i) { + q3.Pop(); + q4.Pop(); + } + EXPECT_TRUE(q3 == q4); +} + +TEST(QueueTest, SizeAfterManyOperations) { + Queue q; + const int N = 100'000; + + for (int i = 0; i < N; ++i) { + q.Push(i); + q.Push(i + 1); + q.Pop(); + } + + EXPECT_EQ(q.Size(), N); +} + +TEST(QueueTest, FifoOrder) { + Queue q; + const int N = 100'000; + + for (int i = 0; i < N; ++i) { + q.Push(i); + } + + for (int i = 0; i < N; ++i) { + EXPECT_EQ(q.Front(), i); + q.Pop(); + } + + EXPECT_TRUE(q.Empty()); +} + +TEST(QueueTest, MixedOperations) { + Queue q; + + q.Push(1); + q.Push(2); + EXPECT_EQ(q.Front(), 1); + EXPECT_EQ(q.Back(), 2); + + q.Push(3); + q.Pop(); + EXPECT_EQ(q.Front(), 2); + EXPECT_EQ(q.Back(), 3); + + q.Push(4); + q.Push(5); + q.Pop(); + EXPECT_EQ(q.Front(), 3); + EXPECT_EQ(q.Back(), 5); + + q.Pop(); + q.Pop(); + EXPECT_EQ(q.Front(), 5); + EXPECT_EQ(q.Back(), 5); + + q.Pop(); + EXPECT_TRUE(q.Empty()); +} + +TEST(QueueTest, ConstQueue) { + const Queue q = {1, 2, 3, 4, 5}; + EXPECT_EQ(q.Size(), 5); + EXPECT_FALSE(q.Empty()); + EXPECT_EQ(q.Front(), 1); + EXPECT_EQ(q.Back(), 5); + + const Queue q2 = {1, 2, 3, 4, 5}; + EXPECT_TRUE(q == q2); + EXPECT_FALSE(q != q2); +} + +TEST(QueueTest, ConstRefQueue) { + Queue q = {1, 2, 3, 4, 5}; + const Queue& q_ref = q; + EXPECT_EQ(q_ref.Size(), 5); + EXPECT_FALSE(q_ref.Empty()); + EXPECT_EQ(q_ref.Front(), 1); + EXPECT_EQ(q_ref.Back(), 5); + EXPECT_TRUE(q_ref == q_ref); + EXPECT_FALSE(q_ref != q_ref); + + q.Pop(); + EXPECT_EQ(q_ref.Size(), 4); + EXPECT_FALSE(q_ref.Empty()); + EXPECT_EQ(q_ref.Front(), 2); + EXPECT_EQ(q_ref.Back(), 5); +} + +TEST(QueueTest, SingleElement) { + Queue q; + + q.Push(42); + EXPECT_EQ(q.Size(), 1); + EXPECT_EQ(q.Front(), 42); + EXPECT_EQ(q.Back(), 42); + EXPECT_FALSE(q.Empty()); + + q.Pop(); + EXPECT_TRUE(q.Empty()); + EXPECT_EQ(q.Size(), 0); +} + +class QueuePerformanceTest : public ::testing::Test { +protected: + void SetUp() override { + } + + void TearDown() override { + } + + template + static long long MeasureTime(Func func) { + auto start = std::chrono::high_resolution_clock::now(); + func(); + auto end = std::chrono::high_resolution_clock::now(); + return std::chrono::duration_cast(end - start).count(); + } +}; + +TEST_F(QueuePerformanceTest, CapacityConstructor) { + constexpr int LARGE_SIZE = 10'000'000; + Queue q_with_reserve(LARGE_SIZE); + + long long time_with_reserve = MeasureTime([&q_with_reserve]() { + for (int i = 0; i < LARGE_SIZE; ++i) { + q_with_reserve.Push(i); + } + }); + + std::cout << "\nВремя заполнения очереди с резервированием (" + << LARGE_SIZE << " элементов): " + << time_with_reserve << " ms" << std::endl; + + Queue q_without_reserve; + long long time_without_reserve = MeasureTime([&q_without_reserve]() { + for (int i = 0; i < LARGE_SIZE; ++i) { + q_without_reserve.Push(i); + } + }); + + std::cout << "Время заполнения очереди без резервирования (" + << LARGE_SIZE << " элементов): " + << time_without_reserve << " ms" << std::endl; + + EXPECT_EQ(q_with_reserve.Size(), LARGE_SIZE); + EXPECT_EQ(q_without_reserve.Size(), LARGE_SIZE); + + for (int i = 0; i < LARGE_SIZE; ++i) { + EXPECT_EQ(q_with_reserve.Front(), i); + EXPECT_TRUE(q_with_reserve.Pop()); + EXPECT_EQ(q_without_reserve.Front(), i); + EXPECT_TRUE(q_without_reserve.Pop()); + } + EXPECT_TRUE(q_with_reserve.Empty()); + EXPECT_TRUE(q_without_reserve.Empty()); + + std::cout << "Разница: " << (time_without_reserve - time_with_reserve) + << " ms (экономия " + << (100.0 * (time_without_reserve - time_with_reserve) / time_without_reserve) + << "%)" << std::endl; + + EXPECT_LT(time_with_reserve * 1.0, time_without_reserve * 0.94); +} + +TEST_F(QueuePerformanceTest, CapacityCtorCompareWithVector) { + constexpr int LARGE_SIZE = 10'000'000; + + Queue q(LARGE_SIZE); + long long queue_push_time = MeasureTime([&q]() { + for (int i = 0; i < LARGE_SIZE; ++i) { + q.Push(i); + } + }); + + std::vector vec; + vec.reserve(LARGE_SIZE); + long long vector_push_time = MeasureTime([&vec]() { + for (int i = 0; i < LARGE_SIZE; ++i) { + vec.push_back(i); + } + }); + + std::cout << "\nСравнение с вектором (" << LARGE_SIZE << " элементов):" << std::endl; + std::cout << "Очередь на двух стеках:" << std::endl; + std::cout << " Push: " << queue_push_time << " ms" << std::endl; + std::cout << "Вектор:" << std::endl; + std::cout << " Push: " << vector_push_time << " ms" << std::endl; + + EXPECT_LT(queue_push_time * 1.0, vector_push_time * 1.5); +} \ No newline at end of file diff --git a/04_week/tasks/ring_buffer/CMakeLists.txt b/04_week/tasks/ring_buffer/CMakeLists.txt new file mode 100644 index 00000000..2893892c --- /dev/null +++ b/04_week/tasks/ring_buffer/CMakeLists.txt @@ -0,0 +1 @@ +add_gtest(test_ring_buffer test.cpp) \ No newline at end of file diff --git a/04_week/tasks/ring_buffer/README.md b/04_week/tasks/ring_buffer/README.md new file mode 100644 index 00000000..37d252d4 --- /dev/null +++ b/04_week/tasks/ring_buffer/README.md @@ -0,0 +1,49 @@ +# Кольцевой буфер + +Необходимо реализовать класс `RingBuffer`, который представляет собой кольцевой буфер +фиксированного размера, построенный на контейнере `std::vector`. + +При добавлении в заполненный буфер перезаписывается самый старый элемент. + +Может хранить либо позиции начала, конца и размер буфера, либо указатели на +соответствующие позиции. + +Класс предоставляет следующий функционал: + +- Конструктор от вместимости буфера +- Конструктор от вместимости буфера и начального значения, заполняет весь буфер +- Конструктор от `std::initializer_list`, вместимость соответствует размеру контейнера. +- Метод `Push` - добавляет элемент в буфер, может перезаписывать +- Метод `TryPush` - пытается добавить элемент без перезаписи, возвращает `true` в случае + успеха и `false` если буфер полон. +- Метод `Pop` - убирает самый старый элемент из буфера +- Метод `TryPop` - пытается извлечь самый старый элемент из буфера и возвращает `true` + в случае успеха и `false` для пустого буфера, и возвращает убранное значение через + параметр метода +- Оператор `[]` - доступ к элементу по логическому индексу (0 - наиболее старый элемент + кольцевого буфера) +- Метод `Front` - обеспечивает доступ на запись и чтение к самому новому элементу буфера +- Метод `Back` - обеспечивает доступ на запись и чтение к самому старому элементу буфера +- Метод `Empty` - возвращает результат проверки буфера на отсутствие элементов +- Метод `Full` - возвращает результат проверки буфера на заполненность +- Метод `Size` - возвращает количество элементов в буфере +- Метод `Capacity` - возвращает количество элементов в буфере +- Метод `Clear` - очищает буфер, позиции начала и конца соответствуют нулевому элементу +- Метод `Resize` - изменяет вместимость буфера, если буфер увеличивается, то все элементы + копируются в новый буфер в правильном порядке, если буфер уменьшается, то отбрасываются + старые элементы, остальные копируются в правильном порядке +- Метод `Vector` - возвращает `std::vector` - линейное представление буфера + +Сконструировать буфер нулевого размера нельзя, создается буфер единичного размера. +Аналогично для метода `Resize`. + +Вызов метода `Pop` от пустой очереди является корректной операцией. + +Вызов `Front` и `Back` от пустой очереди не ограничивается и ведет к **UB**. + +## Примечание + +- Запрещено использовать `std::queue`, `std::deque`, `std::list` +- Рекомендуется определять методы вне класса +- Некоторые методы могут потребовать перегрузки +- При необходимости вспомогательные методы реализуются в закрытой части класса \ No newline at end of file diff --git a/04_week/tasks/ring_buffer/ring_buffer.cpp b/04_week/tasks/ring_buffer/ring_buffer.cpp new file mode 100644 index 00000000..0d82856f --- /dev/null +++ b/04_week/tasks/ring_buffer/ring_buffer.cpp @@ -0,0 +1,174 @@ +#include +#include +#include + +class RingBuffer { +public: + RingBuffer(size_t capacity); + RingBuffer(size_t capacity, int fill); + RingBuffer(std::initializer_list il); + + void Push(int element); + bool TryPush(int element); + void Pop(); + bool TryPop(int& element); + int& Front(); + int& Back(); + bool Empty() const; + bool Full() const; + size_t Size() const; + size_t Capacity() const; + void Clear(); + void Resize(size_t new_capacity); + std::vector Vector() const; + + int& operator[](size_t index); + const int& operator[](size_t index) const; + +private: + std::vector m_data; + size_t m_start_index; + size_t m_end_index; + size_t m_current_count; +}; + +RingBuffer::RingBuffer(size_t capacity) { + if (!capacity) { + capacity = 1; + } + + m_data.resize(capacity); + m_start_index = 0; + m_end_index = 0; + m_current_count = 0; +} + +RingBuffer::RingBuffer(size_t capacity, int fill) : RingBuffer(capacity) { + std::fill(m_data.begin(), m_data.end(), fill); + m_end_index = m_data.size() - 1; + m_current_count = m_data.size(); +} + +RingBuffer::RingBuffer(std::initializer_list il) : RingBuffer(il.size()) { + std::copy(il.begin(), il.end(), m_data.begin()); + m_end_index = il.size() - 1; + m_current_count = il.size(); +} + +void RingBuffer::Push(int element) { + if (!Empty()) { + m_end_index = (m_end_index + 1) % Capacity(); + + if (m_end_index == m_start_index) { + m_start_index = (m_start_index + 1) % Capacity(); + } + } + + m_data[m_end_index] = element; + + if (!Full()) { + ++m_current_count; + } +} + +bool RingBuffer::TryPush(int element) { + if (!Full()) { + Push(element); + return true; + } + + return false; +} + +void RingBuffer::Pop() { + if (!Empty()) { + m_start_index = (m_start_index + 1) % Capacity(); + --m_current_count; + } +} + +bool RingBuffer::TryPop(int& element) { + if (!Empty()) { + element = m_data[m_start_index]; + Pop(); + return true; + } + + return false; +} + +int& RingBuffer::operator[](size_t index) { + return m_data[(m_start_index + index) % Capacity()]; +} + +const int& RingBuffer::operator[](size_t index) const { + return (const_cast(this))->operator[](index); +} + +int& RingBuffer::Front() { + return m_data[m_end_index]; +} + +int& RingBuffer::Back() { + return m_data[m_start_index]; +} + +bool RingBuffer::Empty() const { + return Size() == 0; +} + +bool RingBuffer::Full() const { + return Size() == Capacity(); +} + +size_t RingBuffer::Size() const { + return m_current_count; +} + +size_t RingBuffer::Capacity() const { + return m_data.size(); +} + +void RingBuffer::Clear() { + m_start_index = m_end_index = m_current_count = 0; +} + +void RingBuffer::Resize(size_t new_capacity) { + if (!new_capacity) { + new_capacity = 1; + } + + std::vector vec(new_capacity); + + if (new_capacity >= Size()) { + for (size_t i = 0; i < Size(); ++i) { + vec[i] = m_data[(m_start_index + i) % Capacity()]; + } + + m_end_index = Size() - 1; + } + else { + size_t skip = Size() - new_capacity; + + for (size_t i = 0; i < Size(); ++i) { + vec[i] = m_data[(m_start_index + i + skip) % Capacity()]; + } + + m_end_index = new_capacity - 1; + m_current_count = new_capacity; + } + + m_start_index = 0; + m_data = vec; +} + +std::vector RingBuffer::Vector() const +{ + std::vector vec(m_current_count); + + for (size_t i = 0; i < Size(); ++i) { + vec[i] = m_data[(m_start_index + i) % Capacity()]; + } + + return vec; +} \ No newline at end of file diff --git a/04_week/tasks/ring_buffer/test.cpp b/04_week/tasks/ring_buffer/test.cpp new file mode 100644 index 00000000..8fd2c251 --- /dev/null +++ b/04_week/tasks/ring_buffer/test.cpp @@ -0,0 +1,549 @@ +#include + +#include + +#include "ring_buffer.cpp" + + +TEST(RingBufferTest, CtorCapacity) { + RingBuffer buffer(5); + + EXPECT_EQ(buffer.Capacity(), 5); + EXPECT_EQ(buffer.Size(), 0); + EXPECT_TRUE(buffer.Empty()); + EXPECT_FALSE(buffer.Full()); +} + +TEST(RingBufferTest, CtorCapacityAndInitialValue) { + RingBuffer buffer(5, 42); + + EXPECT_EQ(buffer.Capacity(), 5); + EXPECT_EQ(buffer.Size(), 5); + EXPECT_FALSE(buffer.Empty()); + EXPECT_TRUE(buffer.Full()); + + for (size_t i = 0; i < 5; ++i) { + EXPECT_EQ(buffer[i], 42); + } +} + +TEST(RingBufferTest, CtorInitializerList) { + RingBuffer buffer = {1, 2, 3, 4, 5}; + + EXPECT_EQ(buffer.Capacity(), 5); + EXPECT_EQ(buffer.Size(), 5); + EXPECT_FALSE(buffer.Empty()); + EXPECT_TRUE(buffer.Full()); + + for (size_t i = 0; i < 5; ++i) { + EXPECT_EQ(buffer[i], static_cast(i + 1)); + } +} + +TEST(RingBufferTest, ZeroCapacity) { + { + RingBuffer buffer(0); + EXPECT_EQ(buffer.Capacity(), 1); + EXPECT_EQ(buffer.Size(), 0); + EXPECT_TRUE(buffer.Empty()); + EXPECT_FALSE(buffer.Full()); + } + { + RingBuffer buffer(0, 42); + EXPECT_EQ(buffer.Capacity(), 1); + EXPECT_EQ(buffer.Size(), 1); + EXPECT_FALSE(buffer.Empty()); + EXPECT_TRUE(buffer.Full()); + EXPECT_EQ(buffer[0], 42); + } + { + RingBuffer buffer({}); + EXPECT_EQ(buffer.Capacity(), 1); + EXPECT_EQ(buffer.Size(), 0); + EXPECT_TRUE(buffer.Empty()); + EXPECT_FALSE(buffer.Full()); + } +} + +TEST(RingBufferTest, PushToEmptyBuffer) { + RingBuffer buffer(3); + + buffer.Push(10); + EXPECT_EQ(buffer.Size(), 1); + EXPECT_EQ(buffer.Front(), 10); + EXPECT_EQ(buffer.Back(), 10); + + buffer.Push(20); + EXPECT_EQ(buffer.Size(), 2); + EXPECT_EQ(buffer.Front(), 20); + EXPECT_EQ(buffer.Back(), 10); + + buffer.Push(30); + EXPECT_EQ(buffer.Size(), 3); + EXPECT_TRUE(buffer.Full()); + EXPECT_EQ(buffer.Front(), 30); + EXPECT_EQ(buffer.Back(), 10); +} + +TEST(RingBufferTest, OverflowBehavior) { + RingBuffer buffer(3); + + buffer.Push(1); + buffer.Push(2); + buffer.Push(3); + buffer.Push(4); + + EXPECT_EQ(buffer.Size(), 3); + EXPECT_EQ(buffer.Front(), 4); + EXPECT_EQ(buffer.Back(), 2); + + buffer.Push(5); + EXPECT_EQ(buffer.Front(), 5); + EXPECT_EQ(buffer.Back(), 3); + + buffer.Push(6); + EXPECT_EQ(buffer.Front(), 6); + EXPECT_EQ(buffer.Back(), 4); +} + +TEST(RingBufferTest, PopFromBuffer) { + RingBuffer buffer = {1, 2, 3}; + + buffer.Pop(); + EXPECT_EQ(buffer.Size(), 2); + EXPECT_EQ(buffer.Back(), 2); + + buffer.Pop(); + EXPECT_EQ(buffer.Size(), 1); + EXPECT_EQ(buffer.Back(), 3); + + buffer.Pop(); + EXPECT_TRUE(buffer.Empty()); + EXPECT_EQ(buffer.Size(), 0); + + EXPECT_NO_THROW(buffer.Pop()); + EXPECT_TRUE(buffer.Empty()); +} + +TEST(RingBufferTest, TryPush) { + RingBuffer buffer(2); + + EXPECT_TRUE(buffer.TryPush(1)); + EXPECT_TRUE(buffer.TryPush(2)); + EXPECT_FALSE(buffer.TryPush(3)); + EXPECT_TRUE(buffer.Full()); + EXPECT_EQ(buffer.Size(), 2); + EXPECT_EQ(buffer.Front(), 2); + EXPECT_EQ(buffer.Back(), 1); + + buffer.Pop(); + EXPECT_EQ(buffer.Size(), 1); + EXPECT_TRUE(buffer.TryPush(3)); + EXPECT_EQ(buffer.Size(), 2); + EXPECT_EQ(buffer.Front(), 3); + EXPECT_EQ(buffer.Back(), 2); +} + +TEST(RingBufferTest, TryPop) { + RingBuffer buffer = {10, 20, 30}; + int value; + + EXPECT_TRUE(buffer.TryPop(value)); + EXPECT_EQ(value, 10); + EXPECT_EQ(buffer.Size(), 2); + + EXPECT_TRUE(buffer.TryPop(value)); + EXPECT_EQ(value, 20); + EXPECT_EQ(buffer.Size(), 1); + + EXPECT_TRUE(buffer.TryPop(value)); + EXPECT_EQ(value, 30); + EXPECT_TRUE(buffer.Empty()); + + EXPECT_FALSE(buffer.TryPop(value)); +} + +TEST(RingBufferTest, OperatorIndex) { + RingBuffer buffer(5); + + buffer.Push(10); + buffer.Push(20); + buffer.Push(30); + + EXPECT_EQ(buffer[0], 10); + EXPECT_EQ(buffer[1], 20); + EXPECT_EQ(buffer[2], 30); + + buffer[1] = 200; + EXPECT_EQ(buffer[1], 200); +} + +TEST(RingBufferTest, ConstOperatorIndex) { + const RingBuffer buffer = {1, 2, 3, 4, 5}; + + EXPECT_EQ(buffer[0], 1); + EXPECT_EQ(buffer[2], 3); + EXPECT_EQ(buffer[4], 5); +} + +TEST(RingBufferTest, FrontAndBack) { + RingBuffer buffer(5); + + buffer.Push(1); + EXPECT_EQ(buffer.Front(), 1); + EXPECT_EQ(buffer.Back(), 1); + + buffer.Push(2); + EXPECT_EQ(buffer.Front(), 2); + EXPECT_EQ(buffer.Back(), 1); + + buffer.Push(3); + buffer.Push(4); + buffer.Push(5); + EXPECT_EQ(buffer.Front(), 5); + EXPECT_EQ(buffer.Back(), 1); + + buffer.Front() = 500; + buffer.Back() = 100; + EXPECT_EQ(buffer.Front(), 500); + EXPECT_EQ(buffer.Back(), 100); +} + +TEST(RingBufferTest, EmptyAndFull) { + RingBuffer buffer(3); + + EXPECT_TRUE(buffer.Empty()); + EXPECT_FALSE(buffer.Full()); + + buffer.Push(1); + EXPECT_FALSE(buffer.Empty()); + EXPECT_FALSE(buffer.Full()); + + buffer.Push(2); + buffer.Push(3); + EXPECT_FALSE(buffer.Empty()); + EXPECT_TRUE(buffer.Full()); + + buffer.Pop(); + EXPECT_FALSE(buffer.Empty()); + EXPECT_FALSE(buffer.Full()); + + buffer.Pop(); + buffer.Pop(); + EXPECT_TRUE(buffer.Empty()); + EXPECT_FALSE(buffer.Full()); +} + +TEST(RingBufferTest, SizeAndCapacity) { + RingBuffer buffer(10); + + EXPECT_EQ(buffer.Capacity(), 10); + EXPECT_EQ(buffer.Size(), 0); + + for (int i = 0; i < 7; ++i) { + buffer.Push(i); + } + + EXPECT_EQ(buffer.Capacity(), 10); + EXPECT_EQ(buffer.Size(), 7); + + for (int i = 0; i < 10; ++i) { + buffer.Push(i * 10); + } + + EXPECT_EQ(buffer.Capacity(), 10); + EXPECT_EQ(buffer.Size(), 10); +} + +TEST(RingBufferTest, Clear) { + RingBuffer buffer = {1, 2, 3, 4, 5}; + + EXPECT_FALSE(buffer.Empty()); + buffer.Clear(); + + EXPECT_TRUE(buffer.Empty()); + EXPECT_EQ(buffer.Size(), 0); + EXPECT_EQ(buffer.Capacity(), 5); + + buffer.Push(100); + EXPECT_EQ(buffer.Size(), 1); + EXPECT_EQ(buffer.Front(), 100); + EXPECT_EQ(buffer.Back(), 100); +} + +TEST(RingBufferTest, ResizeIncrease) { + RingBuffer buffer(3); + buffer.Push(1); + buffer.Push(2); + buffer.Push(3); + + EXPECT_EQ(buffer.Capacity(), 3); + EXPECT_EQ(buffer.Size(), 3); + + buffer.Resize(5); + EXPECT_EQ(buffer.Capacity(), 5); + EXPECT_EQ(buffer.Size(), 3); + + EXPECT_EQ(buffer[0], 1); + EXPECT_EQ(buffer[1], 2); + EXPECT_EQ(buffer[2], 3); + + buffer.Push(4); + buffer.Push(5); + EXPECT_EQ(buffer.Size(), 5); + EXPECT_TRUE(buffer.Full()); + EXPECT_EQ(buffer[3], 4); + EXPECT_EQ(buffer[4], 5); +} + +TEST(RingBufferTest, ResizeDecrease) { + RingBuffer buffer(5); + + for (int i = 0; i < 5; ++i) { + buffer.Push(i); + } + + buffer.Resize(3); + + EXPECT_EQ(buffer.Capacity(), 3); + EXPECT_EQ(buffer.Size(), 3); + + EXPECT_EQ(buffer[0], 2); + EXPECT_EQ(buffer[1], 3); + EXPECT_EQ(buffer[2], 4); +} + +TEST(RingBufferTest, ResizeDecreaseForNotFullBuffer) { + RingBuffer buffer(5); + buffer.Push(1); + buffer.Push(2); + + buffer.Resize(3); + + EXPECT_EQ(buffer.Capacity(), 3); + EXPECT_EQ(buffer.Size(), 2); + EXPECT_EQ(buffer[0], 1); + EXPECT_EQ(buffer[1], 2); + + buffer.Resize(5); + buffer.Push(3); + buffer.Push(4); + buffer.Resize(3); + + EXPECT_EQ(buffer.Size(), 3); + EXPECT_EQ(buffer[0], 2); + EXPECT_EQ(buffer[1], 3); + EXPECT_EQ(buffer[2], 4); +} + +TEST(RingBufferTest, ResizeWithOverflow) { + RingBuffer buffer(5); + + for (int i = 0; i < 7; ++i) { + buffer.Push(i); + } + // buffer = {2, 3, 4, 5, 6} + buffer.Resize(3); + + EXPECT_EQ(buffer.Capacity(), 3); + EXPECT_EQ(buffer.Size(), 3); + EXPECT_EQ(buffer[0], 4); + EXPECT_EQ(buffer[1], 5); + EXPECT_EQ(buffer[2], 6); +} + +TEST(RingBufferTest, ResizeToSameSize) { + RingBuffer buffer = {1, 2, 3}; + + buffer.Resize(3); + + EXPECT_EQ(buffer.Capacity(), 3); + EXPECT_EQ(buffer.Size(), 3); + EXPECT_EQ(buffer[0], 1); + EXPECT_EQ(buffer[1], 2); + EXPECT_EQ(buffer[2], 3); +} + +TEST(RingBufferTest, ResizeZero) { + RingBuffer buffer(3); + buffer.Push(1); + + EXPECT_NO_THROW(buffer.Resize(0)); + EXPECT_EQ(buffer.Size(), 1); + EXPECT_EQ(buffer[0], 1); +} + +TEST(RingBufferTest, VectorConversion) { + RingBuffer buffer(5); + + buffer.Push(1); + buffer.Push(2); + buffer.Push(3); + + std::vector vec = buffer.Vector(); + + EXPECT_EQ(vec.size(), 3); + EXPECT_EQ(vec[0], 1); + EXPECT_EQ(vec[1], 2); + EXPECT_EQ(vec[2], 3); + + buffer.Push(4); + buffer.Push(5); + buffer.Push(6); + + vec = buffer.Vector(); + EXPECT_EQ(vec.size(), 5); + EXPECT_EQ(vec[0], 2); + EXPECT_EQ(vec[4], 6); +} + +TEST(RingBufferTest, VectorFromComplexBuffer) { + RingBuffer buffer(4); + + buffer.Push(1); + buffer.Push(2); + buffer.Push(3); + buffer.Push(4); + + buffer.Pop(); + buffer.Pop(); + + buffer.Push(5); + buffer.Push(6); + + std::vector vec = buffer.Vector(); + + EXPECT_EQ(vec.size(), 4); + EXPECT_EQ(vec[0], 3); + EXPECT_EQ(vec[1], 4); + EXPECT_EQ(vec[2], 5); + EXPECT_EQ(vec[3], 6); +} + +TEST(RingBufferTest, CopyConstructor) { + RingBuffer original(5); + original.Push(1); + original.Push(2); + original.Push(3); + + RingBuffer copy(original); + + EXPECT_EQ(copy.Capacity(), 5); + EXPECT_EQ(copy.Size(), 3); + EXPECT_EQ(copy[0], 1); + EXPECT_EQ(copy[1], 2); + EXPECT_EQ(copy[2], 3); + + original.Push(4); + EXPECT_EQ(original.Size(), 4); + EXPECT_EQ(copy.Size(), 3); +} + +TEST(RingBufferTest, CopyAssignment) { + RingBuffer b1(5); + b1.Push(1); + b1.Push(2); + + RingBuffer b2(3); + b2.Push(10); + b2.Push(20); + b2.Push(30); + + b2 = b1; + + EXPECT_EQ(b2.Capacity(), 5); + EXPECT_EQ(b2.Size(), 2); + EXPECT_EQ(b2[0], 1); + EXPECT_EQ(b2[1], 2); + + RingBuffer& ref = b1; + b1 = ref; + EXPECT_EQ(b1.Size(), 2); +} + +TEST(RingBufferTest, ComplexScenario) { + RingBuffer buffer(4); + + buffer.Push(1); + buffer.Push(2); + buffer.Push(3); + buffer.Push(4); + + buffer.Push(5); + buffer.Push(6); + + buffer.Pop(); + buffer.Pop(); + + buffer.Push(7); + buffer.Push(8); + buffer.Push(9); + + EXPECT_EQ(buffer.Size(), 4); + EXPECT_EQ(buffer[0], 6); + EXPECT_EQ(buffer[1], 7); + EXPECT_EQ(buffer[2], 8); + EXPECT_EQ(buffer[3], 9); +} + +TEST(RingBufferTest, ComplexScenarioTwo) { + RingBuffer buffer(3); + + buffer.Push(1); + buffer.Push(2); + buffer.Pop(); + buffer.Push(3); + buffer.Pop(); + buffer.Push(4); + buffer.Push(5); + buffer.Push(6); + buffer.Push(7); + + EXPECT_EQ(buffer.Size(), 3); + EXPECT_EQ(buffer[0], 5); + EXPECT_EQ(buffer[1], 6); + EXPECT_EQ(buffer[2], 7); +} + +TEST(RingBufferTest, LargeBufferOperations) { + constexpr size_t LARGE_SIZE = 1'000'000; + RingBuffer buffer(LARGE_SIZE); + + for (size_t i = 0; i < LARGE_SIZE; ++i) { + buffer.Push(static_cast(i)); + } + + EXPECT_EQ(buffer.Size(), LARGE_SIZE); + EXPECT_TRUE(buffer.Full()); + + for (size_t i = 0; i < LARGE_SIZE; ++i) { + EXPECT_EQ(buffer[i], static_cast(i)); + } + + // Быстрое очищение + while (!buffer.Empty()) { + buffer.Pop(); + } + + EXPECT_TRUE(buffer.Empty()); +} + +TEST(RingBufferTest, WrapAroundBehavior) { + RingBuffer buffer(5); + + for (int i = 0; i < 5; ++i) { + buffer.Push(i + 1); + } + + buffer.Pop(); + buffer.Pop(); + + buffer.Push(6); + buffer.Push(7); + + EXPECT_EQ(buffer[0], 3); + EXPECT_EQ(buffer[1], 4); + EXPECT_EQ(buffer[2], 5); + EXPECT_EQ(buffer[3], 6); + EXPECT_EQ(buffer[4], 7); +} \ No newline at end of file diff --git a/04_week/tasks/stack/CMakeLists.txt b/04_week/tasks/stack/CMakeLists.txt new file mode 100644 index 00000000..5dd310ae --- /dev/null +++ b/04_week/tasks/stack/CMakeLists.txt @@ -0,0 +1 @@ +add_gtest(test_stack test.cpp) \ No newline at end of file diff --git a/04_week/tasks/stack/README.md b/04_week/tasks/stack/README.md new file mode 100644 index 00000000..8d034401 --- /dev/null +++ b/04_week/tasks/stack/README.md @@ -0,0 +1,31 @@ +[Стек]: https://en.wikipedia.org/wiki/Stack_(abstract_data_type) + +# Стек + +[Стек] представляет собой LIFO (Last Input First Output) структуру данных. +Необходимо реализовать класс `Stack`, который представляет собой простую обертку +над классом вектор `std::vector`. + +Класс предоставляет следующие методы: + +- Метод `Push` - добавляет элемент на верхушку стека +- Метод `Pop` - убирает элемент с верхушки стека +- Метод `Top` - обеспечивает доступ к элементу на верхушке стека +- Метод `Empty` - возвращает результат проверки стека на отсутствие элементов +- Метод `Size` - возвращает количество элементов в стеке +- Метод `Clear` - очищает стек +- Метод `Swap` - меняется элементами с другим стеком (без копирования) +- Оператор `==` - сравнение стека на равенство +- Оператор `!=` - сравнение стека на неравенство + +Вызов метода `Pop` от пустого стека является корректной операцией. Метод должен +возвращать `true` или `false` в зависимости от того выполнялась операция или нет. + +## Примечание + +- Запрещено использовать класс `std::stack` +- Значения стека должны находится в закрытой части класса +- Рекомендуется определять методы вне класса +- Некоторые методы могут потребовать перегрузки +- Обратите внимание, что при использовании в качестве полей контейнеров стандартной + библиотеки нет необходимости писать собственные конструкторы \ No newline at end of file diff --git a/04_week/tasks/stack/stack.cpp b/04_week/tasks/stack/stack.cpp new file mode 100644 index 00000000..ea205c3c --- /dev/null +++ b/04_week/tasks/stack/stack.cpp @@ -0,0 +1,68 @@ +#include +#include + +class Stack { +public: + Stack() {} + + void Push(int element); + bool Pop(); + int& Top(); + const int& Top() const; + bool Empty() const; + size_t Size() const; + void Clear(); + void Swap(Stack& other); + + bool operator==(const Stack& other) const; + bool operator!=(const Stack& other) const; + +private: + std::vector m_data; +}; + +void Stack::Push(int element) { + m_data.push_back(element); +} + +bool Stack::Pop() { + if (m_data.empty()) { + return false; + } + + m_data.pop_back(); + + return true; +} + +int& Stack::Top() { + return m_data.back(); +} + +const int& Stack::Top() const { + return (const_cast(this))->Top(); +} + +bool Stack::Empty() const { + return m_data.empty(); +} + +size_t Stack::Size() const { + return m_data.size(); +} + +void Stack::Clear() { + m_data.clear(); +} + +void Stack::Swap(Stack& other) { + std::swap(m_data, other.m_data); +} + +bool Stack::operator==(const Stack& other) const { + return this->m_data == other.m_data; +} + +bool Stack::operator!=(const Stack& other) const { + return !(*this == other); +} \ No newline at end of file diff --git a/04_week/tasks/stack/test.cpp b/04_week/tasks/stack/test.cpp new file mode 100644 index 00000000..1b1cb95f --- /dev/null +++ b/04_week/tasks/stack/test.cpp @@ -0,0 +1,353 @@ +#include + +#include + +#include "stack.cpp" + + +TEST(StackTest, EmptyStack) { + Stack s; + + EXPECT_TRUE(s.Empty()); + EXPECT_EQ(s.Size(), 0); +} + +TEST(StackTest, PushElements) { + Stack s; + + s.Push(1); + EXPECT_FALSE(s.Empty()); + EXPECT_EQ(s.Size(), 1); + EXPECT_EQ(s.Top(), 1); + + s.Push(-1); + EXPECT_EQ(s.Size(), 2); + EXPECT_EQ(s.Top(), -1); + + s.Push(9); + EXPECT_EQ(s.Size(), 3); + EXPECT_EQ(s.Top(), 9); +} + +TEST(StackTest, PopElements) { + Stack s; + s.Push(1); + s.Push(-1); + s.Push(9); + + EXPECT_EQ(s.Top(), 9); + EXPECT_TRUE(s.Pop()); + EXPECT_EQ(s.Size(), 2); + EXPECT_EQ(s.Top(), -1); + + EXPECT_TRUE(s.Pop()); + EXPECT_EQ(s.Size(), 1); + EXPECT_EQ(s.Top(), 1); + + EXPECT_TRUE(s.Pop()); + EXPECT_TRUE(s.Empty()); + EXPECT_EQ(s.Size(), 0); +} + +TEST(StackTest, PopFromEmptyStack) { + Stack s; + EXPECT_TRUE(s.Empty()); + EXPECT_EQ(s.Size(), 0); + + EXPECT_FALSE(s.Pop()); + EXPECT_FALSE(s.Pop()); + + EXPECT_TRUE(s.Empty()); + EXPECT_EQ(s.Size(), 0); +} + +TEST(StackTest, TopElement) { + Stack s; + + s.Push(42); + EXPECT_EQ(s.Top(), 42); + + s.Top() = 100; + EXPECT_EQ(s.Top(), 100); +} + +TEST(StackTest, TopElementForConstStack) { + Stack s; + + s.Push(42); + s.Top() = 100; + + const Stack& const_ref = s; + EXPECT_EQ(const_ref.Top(), 100); +} + +TEST(StackTest, TopOnEmptyStack) { + Stack s; + EXPECT_NO_THROW(s.Top()); + + const Stack& const_ref = s; + EXPECT_NO_THROW(const_ref.Top()); +} + +TEST(StackTest, SwapStacks) { + Stack s1; + s1.Push(1); + s1.Push(2); + s1.Push(3); + + Stack s2; + s2.Push(4); + s2.Push(5); + + EXPECT_EQ(s1.Size(), 3); + EXPECT_EQ(s1.Top(), 3); + EXPECT_EQ(s2.Size(), 2); + EXPECT_EQ(s2.Top(), 5); + + s1.Swap(s2); + EXPECT_EQ(s1.Size(), 2); + EXPECT_EQ(s1.Top(), 5); + EXPECT_EQ(s2.Size(), 3); + EXPECT_EQ(s2.Top(), 3); + + s1.Swap(s2); + EXPECT_EQ(s1.Size(), 3); + EXPECT_EQ(s1.Top(), 3); + EXPECT_EQ(s2.Size(), 2); + EXPECT_EQ(s2.Top(), 5); +} + +TEST(StackTest, SwapWithEmptyStack) { + Stack s1; + s1.Push(1); + s1.Push(2); + + Stack s2; + + s1.Swap(s2); + EXPECT_TRUE(s1.Empty()); + EXPECT_EQ(s1.Size(), 0); + EXPECT_EQ(s2.Size(), 2); + EXPECT_EQ(s2.Top(), 2); +} + +TEST(StackTest, SwapWithSelf) { + Stack s; + s.Push(1); + s.Push(2); + + EXPECT_NO_THROW(s.Swap(s)); + EXPECT_EQ(s.Size(), 2); + EXPECT_EQ(s.Top(), 2); +} + +TEST(StackTest, ClearStack) { + Stack s; + s.Push(1); + s.Push(2); + s.Push(3); + + EXPECT_EQ(s.Size(), 3); + + s.Clear(); + + EXPECT_TRUE(s.Empty()); + EXPECT_EQ(s.Size(), 0); +} + +TEST(StackTest, CompareStacks) { + Stack s1; + s1.Push(1); + s1.Push(2); + + Stack s2; + s2.Push(1); + s2.Push(2); + + Stack s3; + s3.Push(1); + s3.Push(3); + + EXPECT_TRUE(s1 == s1); + EXPECT_TRUE(s1 == s2); + EXPECT_FALSE(s1 == s3); + + EXPECT_FALSE(s1 != s1); + EXPECT_FALSE(s1 != s2); + EXPECT_TRUE(s1 != s3); +} + +TEST(StackTest, CompareStacksWithDifferentSize) { + Stack s1; + s1.Push(1); + s1.Push(2); + s1.Push(3); + + Stack s2; + s2.Push(2); + s2.Push(3); + + EXPECT_FALSE(s1 == s2); + EXPECT_TRUE(s1 != s2); +} + +TEST(StackTest, CompareEmptyStacks) { + Stack s4; + Stack s5; + EXPECT_TRUE(s4 == s5); + EXPECT_FALSE(s4 != s5); +} + +TEST(StackTest, LifoBehavior) { + Stack s; + for (int i = 0; i < 5; ++i) { + s.Push(i); + EXPECT_EQ(s.Top(), i); + } + for (int i = 5; i > 0; --i) { + EXPECT_EQ(s.Top(), i - 1); + s.Pop(); + } + EXPECT_TRUE(s.Empty()); +} + +TEST(StackTest, MultipleOperations) { + Stack s; + + s.Push(10); + s.Push(20); + s.Pop(); + EXPECT_EQ(s.Top(), 10); + + s.Push(30); + s.Push(40); + EXPECT_EQ(s.Size(), 3); + + s.Pop(); + s.Pop(); + EXPECT_EQ(s.Top(), 10); + EXPECT_EQ(s.Size(), 1); + + s.Pop(); + EXPECT_TRUE(s.Empty()); +} + +TEST(StackTest, ManyElements) { + Stack s; + const int COUNT = 1'000'000; + + for (int i = 0; i < COUNT; ++i) { + s.Push(i); + EXPECT_EQ(s.Top(), i); + EXPECT_EQ(s.Size(), i + 1); + } + + for (int i = COUNT - 1; i >= 0; --i) { + EXPECT_EQ(s.Top(), i); + s.Pop(); + } + + EXPECT_TRUE(s.Empty()); + EXPECT_EQ(s.Size(), 0); +} + +TEST(StackTest, ConstMethods) { + Stack s; + s.Push(1); + s.Push(2); + s.Push(3); + + const Stack& cs = s; + + EXPECT_FALSE(cs.Empty()); + EXPECT_EQ(cs.Size(), 3); + EXPECT_EQ(cs.Top(), 3); + EXPECT_TRUE(cs == cs); + EXPECT_FALSE(cs != cs); +} + +TEST(StackTest, CopyCtor) { + Stack original; + original.Push(1); + original.Push(2); + original.Push(3); + + Stack copy(original); + + EXPECT_EQ(copy.Size(), 3); + EXPECT_EQ(copy.Top(), 3); + EXPECT_TRUE(copy == original); +} + +TEST(StackTest, CopyCtorWithCopyModification) { + Stack original; + original.Push(1); + original.Push(2); + original.Push(3); + + Stack copy(original); + copy.Pop(); + EXPECT_EQ(copy.Size(), 2); + EXPECT_EQ(copy.Top(), 2); + EXPECT_EQ(original.Size(), 3); + EXPECT_EQ(original.Top(), 3); + EXPECT_FALSE(copy == original); +} + +TEST(StackTest, CopyAssignmentOperator) { + Stack s1; + s1.Push(10); + s1.Push(20); + s1.Push(30); + + Stack s2; + s2.Push(40); + s2.Push(50); + + s2 = s1; + EXPECT_EQ(s2.Size(), 3); + EXPECT_EQ(s2.Top(), 30); + EXPECT_TRUE(s2 == s1); + + s2.Pop(); + EXPECT_EQ(s2.Size(), 2); + EXPECT_EQ(s2.Top(), 20); + EXPECT_EQ(s1.Size(), 3); + EXPECT_EQ(s1.Top(), 30); + EXPECT_FALSE(s2 == s1); +} + +TEST(StackTest, CopyEmptyStack) { + Stack empty1; + Stack empty2(empty1); + + EXPECT_TRUE(empty2.Empty()); + EXPECT_EQ(empty2.Size(), 0); + + Stack empty3; + empty3 = empty1; + + EXPECT_TRUE(empty3.Empty()); + EXPECT_EQ(empty3.Size(), 0); +} + +TEST(StackTest, ChainAssignment) { + Stack s1, s2, s3; + + s1.Push(1); + s1.Push(2); + + s3 = s2 = s1; + + EXPECT_TRUE(s1 == s2); + EXPECT_TRUE(s2 == s3); + EXPECT_TRUE(s1 == s3); + + EXPECT_EQ(s1.Size(), 2); + EXPECT_EQ(s2.Size(), 2); + EXPECT_EQ(s3.Size(), 2); + + EXPECT_EQ(s1.Top(), 2); + EXPECT_EQ(s2.Top(), 2); + EXPECT_EQ(s3.Top(), 2); +} \ No newline at end of file diff --git a/grading/deadlines.json b/grading/deadlines.json index 7c07c7e8..ac7682be 100644 --- a/grading/deadlines.json +++ b/grading/deadlines.json @@ -64,5 +64,51 @@ "max_score": 200, "deadline": "2025-12-09 23:59", "description": "Форматированный вывод массива" + }, + + "data_stats": { + "max_score": 100, + "deadline": "2025-12-18 23:59", + "description": "Статистика данных" + }, + "unique": { + "max_score": 100, + "deadline": "2025-12-18 23:59", + "description": "Уникальные элементы" + }, + "range": { + "max_score": 100, + "deadline": "2025-12-18 23:59", + "description": "Диапазон элементов" + }, + "minmax": { + "max_score": 100, + "deadline": "2025-12-18 23:59", + "description": "Минимум и максимум" + }, + "find_all": { + "max_score": 100, + "deadline": "2025-12-18 23:59", + "description": "Найти все элементы" + }, + "os_overload": { + "max_score": 100, + "deadline": "2025-12-18 23:59", + "description": "Перегрузка оператора вывода" + }, + "easy_compare": { + "max_score": 100, + "deadline": "2025-12-18 23:59", + "description": "Оператор сравнения" + }, + "filter": { + "max_score": 100, + "deadline": "2025-12-18 23:59", + "description": "Фильтрация данных" + }, + "enum_operators": { + "max_score": 200, + "deadline": "2025-12-18 23:59", + "description": "Переопределение побитовых операторов" } } \ No newline at end of file