diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..95be4dd --- /dev/null +++ b/.clang-format @@ -0,0 +1,9 @@ +BasedOnStyle: Microsoft +IndentWidth: 4 +UseTab: Always +AccessModifierOffset: 0 +AccessModifierOffset: -4 +AlwaysBreakTemplateDeclarations: Yes +# IndentAccessModifiers: false +# does not work +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..c226389 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,65 @@ +on: push + +jobs: + # check: + # runs-on: ubuntu-latest + # container: mikhail2001/young_devs_image + # steps: + # - uses: actions/checkout@v2 + # - name: GO LINTER CHECK + # run: make check || exit 0 + build: + runs-on: ubuntu-latest + container: mikhail2001/young_devs_image + steps: + - uses: actions/checkout@v2 + - name: GO BUILD + run: make build + - name: Upload artifacts (EXECUTABLE FILE) + uses: actions/upload-artifact@v2 + with: + name: executable-file + path: build/fib + test: + runs-on: ubuntu-latest + container: mikhail2001/young_devs_image + steps: + - uses: actions/checkout@v2 + - run: make build TEST_OPT=ON + - run: make run + - run: ./build/tests/test_set --gtest_filter=TestBasicsFunctional* + - name: GO MY TESTS COVERAGE + run: | + make lcov + lcov --remove ./build/coverage.info -o ./build/coverage.info '/usr/include/*' '/usr/lib/*' $(echo $(pwd)/tests/\*) + make genhtml + - name: Upload artifacts (COVERAGE REPORT) + uses: actions/upload-artifact@v2 + with: + name: coverage-report-my-tests + path: build/report + - run: make build TEST_OPT=ON + - run: make run + - run: make test + - name: GO ALL TESTS COVERAGE + run: | + make lcov + lcov --remove ./build/coverage.info -o ./build/coverage.info '/usr/include/*' '/usr/lib/*' $(echo $(pwd)/tests/\*) + make genhtml + - name: Upload artifacts (COVERAGE REPORT) + uses: actions/upload-artifact@v2 + with: + name: coverage-report-all-tests + path: build/report + - name: GO VALGRIND TESTS + run: make valgrind_tests + - name: GO VALGRIND TARGET + run: make valgrind_target + - name: GO SANITIZER + run: | + make build SANITIZE_OPT=ON + make run + - name: GO SCAN-BUILD + run: | + make build + make scan_build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a1afa60 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/build +/.vscode +test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..28e115e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.0.0) +project(main) + +set(CMAKE_CXX_STANDARD 17) + +find_package(Threads REQUIRED) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") + +option(TEST_OPT "build test version" OFF) +option(DEBUG_OPT "build debug version" ON) +option(SANITIZE_OPT "build with flags -fsanitize" OFF) + +add_subdirectory(set_lib) + +add_executable(${PROJECT_NAME} main.cpp) + +if(TEST_OPT) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage -fPIC -O0") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -coverage -lgcov" ) +endif() + +if(DEBUG_OPT) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -g2 -fPIC -O0") +endif() + +if(SANITIZE_OPT) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address,undefined" ) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined") +endif() + + +target_include_directories(${PROJECT_NAME} PUBLIC ${SET_LIB_INCLUDE_DIRS}) +target_link_libraries(${PROJECT_NAME} PRIVATE ${SET_LIB_LIBRARIES}) + +if (TEST_OPT) + enable_testing() + add_subdirectory(tests) +endif() diff --git a/CPPLINT.cfg b/CPPLINT.cfg new file mode 100644 index 0000000..de5668d --- /dev/null +++ b/CPPLINT.cfg @@ -0,0 +1,10 @@ +headers=h +linelength=110 +filter=-whitespace/tab +filter=-runtime/int +filter=-legal/copyright +filter=-build/header_guard +filter=-build/include_subdir +filter=-build/include +filter=-readability/casting +filter=-whitespace/braces diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9d24918 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM ubuntu:latest +RUN apt update -y && \ + apt install -y wget g++ make binutils cmake cppcheck clang-tidy python3-pip libc6-dbg cmake libgtest-dev lcov clang-tools vim +RUN apt install -y libboost-dev libboost-all-dev +RUN apt install -y clang-format +RUN apt install -y git curl +RUN pip install cpplint +RUN wget https://sourceware.org/pub/valgrind/valgrind-3.18.1.tar.bz2 && \ + tar xfv valgrind-3.18.1.tar.bz2 && \ + cd valgrind-3.18.1 && \ + ./autogen.sh && \ + ./configure && \ + make && \ + make install diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..21d3615 --- /dev/null +++ b/Makefile @@ -0,0 +1,55 @@ +BUILD_DIR = build +TESTS_DIR = tests +LIB_DIR = set_lib + +# project(xxx) on CMakeLists +TESTS_EXE = test_set +TARGET_EXE = main + +PATH_LINTERS_SCRIPT = linters/run.sh + +TEST_OPT = OFF +DEBUG_OPT = OFF +SANITIZE_OPT = OFF + +.PHONY: check build test clean run coverage + +# Изменения в CMakeLists требует make build +# make build = cmake && cmake --build +build: clean + mkdir ${BUILD_DIR} + cd ${BUILD_DIR} && cmake .. -DTEST_OPT=${TEST_OPT} -DDEBUG_OPT=${DEBUG_OPT} -DSANITIZE_OPT=${SANITIZE_OPT} && $(MAKE) --no-print-directory + +clean: + (rm -r ${BUILD_DIR} 2>/dev/null) || exit 0 + +# инкрементальная сборка и запуск исполняемого файла +run: + cd ${BUILD_DIR} && $(MAKE) --no-print-directory + ./${BUILD_DIR}/${TARGET_EXE} + +# запуск исполняемого файла с тестированием +test: + ./${BUILD_DIR}/${TESTS_DIR}/${TESTS_EXE} + +# проверка исходного кода +check: + chmod u+x ${PATH_LINTERS_SCRIPT} && ./${PATH_LINTERS_SCRIPT} + +lcov: + cd ${BUILD_DIR} && lcov -t "testing_${LIB_DIR}" -o coverage.info -c -d ./${TESTS_DIR}/ +genhtml: + cd ${BUILD_DIR} && genhtml -o report coverage.info + +valgrind_tests: + valgrind --tool=memcheck --leak-check=yes ./${BUILD_DIR}/${TESTS_DIR}/${TESTS_EXE} + +valgrind_target: + valgrind --tool=memcheck -s --leak-check=yes --error-exitcode=1 ./${BUILD_DIR}/${TARGET_EXE} + +scan_build: + cd ${BUILD_DIR} && scan-build $(MAKE) --no-print-directory + +formating: + clang-format -i -style=file main.cpp ${LIB_DIR}/include/*.h ${LIB_DIR}/include/*.hpp ${LIB_DIR}/src/*.cpp ${TESTS_DIR}/*.cpp + diff --git a/README.md b/README.md index ac390f9..7d131a7 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,23 @@ # techopark_cpp_hw Репозиторий для сдачи домашних работ по С++ в Технопарке + +### Мяделец Михаил WEB-11 +**STL-совместимый контейнер** + +Необходимо реализовать упрощённую версию упорядоченного множества из STL Set. Асимптотики всех операций должны быть аналогичными std::set. Сравнение элементов типа T осуществлять только при помощи оператора <. +Необходимо поддержать следующие методы: +1) методы жизненного цикла - 4б +- конструктор по умолчанию; +- конструктор, принимающий пару итераторов элементов типа T, последовательно вставляемых в контейнер; +- конструктор, принимающий std::initializer_list элементов типа T; +- конструктор копирования ("глубокое копирование всех узлов контейнера"); +- оператор присваивания; +- деструктор; + +2) класс должен предоставлять const bidirectional-итератор для доступа к элементам, а также методы begin и end, и позволять просматривать все элементы контейнера без возможности их изменения, перемещаясь от каждого к следующему/предыдущему за O(1). Так как контейнер упорядоченный, то подразумевается их перебор в порядке возрастания - 4б; + +3) методы insert и erase вставки и удаления элементов из контейнера. Тип возвращаемого значения - void, При наличии/отсутствии элемента ничего делать не нужно. При модификации контейнера любой из этих операций итераторы могут инвалидироваться - 3б; + +4) константные методы: +- size и empty, возвращающие количество элементов и true/false в зависимости от наличия элементов в контейнере - 1б; +- find и lower_bound, которые возвращают соответственно итератор на искомый элемент во множестве/первый элемент, который не меньше искомого (или end() при его отсутствии) - 3б. diff --git a/linters/run.sh b/linters/run.sh new file mode 100644 index 0000000..4e1ef63 --- /dev/null +++ b/linters/run.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash + +set -o pipefail + +SRC_PATHS="main.cpp set_lib/src/*.cpp" +INCLUDE_PATHS="set_lib/include/*.h" +INCLUDE_PATHS_HPP="set_lib/include/*.hpp" +TESTS_PATHS="tests/*.cpp" + +INCLUDE_DIRECTORIES="set_lib/include" + +function print_header() { + echo -e "\n***** ${1} *****" +} + +function check_log() { + LOG=$( { ${1}; } 2>&1 ) + STATUS=$? + echo "$LOG" + if echo "$LOG" | grep -q -E "${2}" + then + exit 1 + fi + + if [ $STATUS -ne 0 ] + then + exit $STATUS + fi +} + +# ********** cppcheck ********** +print_header "RUN cppcheck" +check_log "cppcheck ${SRC_PATHS} ${INCLUDE_PATHS} ${INCLUDE_PATHS_HPP} ${TESTS_PATHS} --enable=all --inconclusive --error-exitcode=1 -I${INCLUDE_DIRECTORIES} --suppress=missingIncludeSystem" "\(information\)" + +# # ********** clang-tidy ********** +print_header "RUN clang-tidy" +check_log "clang-tidy ${SRC_PATHS} ${TESTS_PATHS} -warnings-as-errors=* -extra-arg=-std=c++17 -- -I${INCLUDE_DIRECTORIES} -x c++" "Error (?:reading|while processing)" + + +# # ********** cpplint ********** +print_header "RUN cpplint" +check_log "cpplint --extensions=cpp ${SRC_PATHS} ${TESTS_PATHS}" "Can't open for reading" +check_log "cpplint --extensions=h ${INCLUDE_PATHS} ${INCLUDE_PATHS_HPP}" "Can't open for reading" + + +# # ********** clang-format ********** +print_header "RUN clang-format" +diff <(clang-format --style=Microsoft ${SRC_PATHS} ${INCLUDE_PATHS} ${INCLUDE_PATHS_HPP} ${TESTS_PATHS}) <(cat ${SRC_PATHS} ${INCLUDE_PATHS} ${INCLUDE_PATHS_HPP} ${TESTS_PATHS}) || exit 1 + +print_header "SUCCESS" \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..e48653b --- /dev/null +++ b/main.cpp @@ -0,0 +1,15 @@ +#include "iterator.h" +#include "set.h" +#include + +using std::cout; +using std::endl; + +int main(int argc, const char *argv[]) +{ + Set set = {1, 5, 3, 7, 34, 88, 23}; + // for (auto elem : set) + // { + // std::cout << elem << std::endl; + // } +} \ No newline at end of file diff --git a/set_lib/CMakeLists.txt b/set_lib/CMakeLists.txt new file mode 100644 index 0000000..990fa14 --- /dev/null +++ b/set_lib/CMakeLists.txt @@ -0,0 +1,42 @@ +cmake_minimum_required(VERSION 3.0.0) +project(set_lib) + +set(CMAKE_CXX_STANDARD 17) +# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic -Werror") + +find_package(Threads REQUIRED) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") + +if(TEST_OPT) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage -fPIC -O0") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -coverage -lgcov" ) +endif() + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage -O0") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -coverage -lgcov" ) + +if(DEBUG_OPT) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g2 -fPIC -O0") +endif() + +if(NOT DEBUG_OPT AND NOT TEST_OPT) + # релиз-версия + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -O3") +endif() + +if(SANITIZE_OPT) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address,undefined" ) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined") +endif() + +file(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) +file(GLOB INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/include) + +add_library(${PROJECT_NAME} ${SOURCES}) +target_include_directories(${PROJECT_NAME} PUBLIC ${INCLUDE_DIRS}) + +message("SOURCES = ${SOURCES}") +message("INCLUDE_DIRS = ${INCLUDE_DIRS}") + +set(SET_LIB_LIBRARIES ${PROJECT_NAME} PARENT_SCOPE) +set(SET_LIB_INCLUDE_DIRS ${INCLUDE_DIRS} PARENT_SCOPE) diff --git a/set_lib/include/iterator.h b/set_lib/include/iterator.h new file mode 100644 index 0000000..c24715f --- /dev/null +++ b/set_lib/include/iterator.h @@ -0,0 +1,46 @@ +#pragma once + +#include "set.h" + +template +class Iterator +{ +public: + using key_type = T; + using value_type = T; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using key_compare = Cmp; + using value_compare = Cmp; + using reference = T &; + using const_reference = const T &; + using pointer = T *; + using const_pointer = const T *; + using iterator_category = std::bidirectional_iterator_tag; + + Iterator(typename Set::Node *node, typename Set::Node *root, bool after_end = false, + bool before_begin = false); + Iterator() = default; + + Iterator &operator=(const Iterator &) = default; + + Iterator &operator++(); + Iterator operator++(int); + Iterator &operator--(); + Iterator operator--(int); + + const_reference operator*(); + const_pointer operator->(); + + bool operator==(const Iterator &); + bool operator!=(const Iterator &); + +private: + typename Set::Node *node; + typename Set::Node *root; + // признаки местоположения итератора перед первым элементом и после последнего + bool before_begin = false; + bool after_end = false; +}; + +#include "iterator_definition.hpp" diff --git a/set_lib/include/iterator_definition.hpp b/set_lib/include/iterator_definition.hpp new file mode 100644 index 0000000..93a6556 --- /dev/null +++ b/set_lib/include/iterator_definition.hpp @@ -0,0 +1,98 @@ +#pragma once + +#include + +#include "iterator.h" + +template +Iterator::Iterator(typename Set::Node *node, typename Set::Node *root, bool after_end, + bool before_begin) + : node(node), root(root), before_begin(before_begin), after_end(after_end) +{ +} + +template +Iterator &Iterator::operator++() +{ + if (node) + { + node = node->next; + if (!node) + { + after_end = true; + } + } + else if (before_begin) + { + node = Set::getLeftMost(root); + before_begin = false; + } + return *this; +} + +template +Iterator Iterator::operator++(int) +{ + Iterator tmp(*this); + ++(*this); + return tmp; +} + +template +Iterator &Iterator::operator--() +{ + if (node) + { + node = node->prev; + if (!node) + { + before_begin = true; + } + } + else if (after_end) + { + node = Set::getRightMost(root); + after_end = false; + } + return *this; +} + +template +Iterator Iterator::operator--(int) +{ + Iterator tmp(*this); + --(*this); + return tmp; +} + +template +const T &Iterator::operator*() +{ + if (!node) + { + throw std::runtime_error("operator*() to nullptr"); + } + return node->data; +} + +template +const T *Iterator::operator->() +{ + if (!node) + { + throw std::runtime_error("operator->() to nullptr"); + } + return &(node->data); +} + +template +bool Iterator::operator==(const Iterator &other) +{ + return node == other.node; +} + +template +bool Iterator::operator!=(const Iterator &other) +{ + return !(*this == other); +} diff --git a/set_lib/include/set.h b/set_lib/include/set.h new file mode 100644 index 0000000..7be0748 --- /dev/null +++ b/set_lib/include/set.h @@ -0,0 +1,137 @@ +#pragma once + +#include +#include +#include +#include + +template +class Iterator; + +template > +class Set +{ +public: + using key_type = T; + using value_type = T; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using key_compare = Cmp; + using value_compare = Cmp; + using reference = T &; + using const_reference = const T &; + using iterator = Iterator; + +private: + struct Node + { + Node(const T &data) + : data(data), height(1), left(nullptr), right(nullptr), parent(nullptr), next(nullptr), prev(nullptr) + { + } + ~Node() + { + left = right = parent = next = prev = nullptr; + } + + T data; + size_t height; + // для АВЛ-дерева + Node *left; + Node *right; + Node *parent; + // для двусвязного спика + Node *next; + Node *prev; + }; + +public: + Set(Cmp cmp = Cmp{}); + template ::value_type, T>::value>::type> + Set(InputIt first, InputIt last, Cmp cmp = Cmp{}); + Set(std::initializer_list const &init_list); + template + Set(Set const &other); + Set(Set const &other); + Set &operator=(Set other); + ~Set(); + size_type size() const; + bool empty() const; + + void insert(const_reference data); + void erase(const_reference data); + + iterator find(const_reference data) const; + iterator lower_bound(const_reference data) const; + + iterator begin() const; + iterator end() const; + + bool operator==(Set const &other) const; + bool operator!=(Set const &other) const; + +private: + friend class Iterator; + // корень дерева + Node *m_root = nullptr; + // узлы двусвязного списка + Node *m_first = nullptr; + std::optional m_first_value = std::nullopt; + Node *m_last = nullptr; + std::optional m_last_value = std::nullopt; + + Cmp m_cmp; + size_type m_size = 0; + + void setNodeEnd(); + + Node *findInternal(const_reference data) const; + Node *lower_boundInternal(const_reference data) const; + Node *eraseInternal(Node *node, const_reference data); + Node *insertInternal(Node *node, const_reference data); + + Node *detachReplacement(Node *node); + Node *findReplacement(Node *node) const; + + Node *rotateLeft(Node *node); + Node *rotateRight(Node *node); + + size_t getHeight(Node *node) const; + void fixHeight(Node *node); + + int getBalance(Node *node) const; + Node *doBalance(Node *node); + + Node *nextInternal(Node *node) const; + Node *prevInternal(Node *node) const; + + void Swap(Set &other); + + static Node *getLeftMost(Node *node) + { + if (!node) + { + return nullptr; + } + while (node->left) + { + node = node->left; + } + return node; + } + static Node *getRightMost(Node *node) + { + if (!node) + { + return nullptr; + } + while (node->right) + { + node = node->right; + } + return node; + } +}; + +#include "set_definition.hpp" diff --git a/set_lib/include/set_definition.hpp b/set_lib/include/set_definition.hpp new file mode 100644 index 0000000..30f1169 --- /dev/null +++ b/set_lib/include/set_definition.hpp @@ -0,0 +1,470 @@ +#pragma once + +#include "set.h" + +template +Set::Set(Cmp cmp) : m_cmp(cmp), m_root(nullptr) +{ +} + +template +template +Set::Set(InputIt first, InputIt last, Cmp cmp) : m_cmp(cmp) +{ + for (; first != last; ++first) + { + insert(*first); + } +} + +template +Set::Set(std::initializer_list const &init_list) +{ + for (auto &&elem : init_list) + { + insert(elem); + } +} + +template +template +Set::Set(Set const &other) +{ + for (auto elem : other) + { + insert(elem); + } +} + +template +Set::Set(Set const &other) +{ + for (auto elem : other) + { + insert(elem); + } +} + +template +Set::~Set() +{ + // итерируемся по списку + Node *node = m_first; + while (node) + { + Node *tmp = node->next; + delete node; + node = tmp; + } +} + +template +Set &Set::operator=(Set other) +{ + Swap(other); + return *this; +} + +template +void Set::Swap(Set &other) +{ + std::swap(m_root, other.m_root); + std::swap(m_first, other.m_first); + std::swap(m_last, other.m_last); + std::swap(m_cmp, other.m_cmp); + std::swap(m_size, other.m_size); +} + +template +void Set::insert(const T &data) +{ + if (findInternal(data) != nullptr) + { + return; + } + ++m_size; + m_root = insertInternal(m_root, data); + auto ptr = findInternal(data); + // обновление m_first + if (!m_first_value || m_cmp(ptr->data, *m_first_value)) + { + m_first = ptr; + m_first_value = ptr->data; + } + // обновление m_last + if (!m_last_value || m_cmp(*m_last_value, ptr->data)) + { + m_last = ptr; + m_last_value = ptr->data; + } + // установка prev и next + ptr->prev = prevInternal(ptr); + if (ptr->prev) + { + ptr->prev->next = ptr; + } + ptr->next = nextInternal(ptr); + if (ptr->next) + { + ptr->next->prev = ptr; + } +} + +template +Iterator Set::find(const T &data) const +{ + auto ptr = findInternal(data); + return ptr ? Iterator(ptr, m_root) : end(); +} + +template +Iterator Set::lower_bound(const T &data) const +{ + auto ptr = lower_boundInternal(data); + return ptr ? Iterator(ptr, m_root) : end(); +} + +template +typename Set::Node *Set::findInternal(const T &data) const +{ + Node *node = m_root; + while (node) + { + if (!m_cmp(node->data, data) && !m_cmp(data, node->data)) + { + return node; + } + else if (m_cmp(node->data, data)) + { + node = node->right; + } + else + { + node = node->left; + } + } + return nullptr; +} + +template +typename Set::Node *Set::lower_boundInternal(const T &data) const +{ + Node *node = m_root; + Node *result = nullptr; + while (node) + { + if (m_cmp(node->data, data)) + { + node = node->right; + } + else + { + result = node; + node = node->left; + } + } + return result; +} + +template +void Set::erase(const T &data) +{ + auto ptr = findInternal(data); + if (ptr != nullptr) + { + auto prevNode = prevInternal(ptr); + auto nextNode = nextInternal(ptr); + // извлекаем из списка + if (!(prevNode == nullptr && nextNode == nullptr)) + { + if (prevNode == nullptr) + { + nextNode->prev = nullptr; + } + if (nextNode == nullptr) + { + prevNode->next = nullptr; + } + if (prevNode && nextNode) + { + prevNode->next = nextNode; + nextNode->prev = prevNode; + } + } + --m_size; + // извлекаем из дерева и удаляем + m_root = eraseInternal(m_root, data); + m_first = getLeftMost(m_root); + } +} + +template +typename Set::Node *Set::eraseInternal(Node *node, const T &data) +{ + if (!node) return nullptr; + if (m_cmp(node->data, data)) + { + node->right = eraseInternal(node->right, data); + if (node->right) + { + node->right->parent = node; + } + } + else if (m_cmp(data, node->data)) + { + node->left = eraseInternal(node->left, data); + if (node->left) + { + node->right->parent = node; + } + } + else + { + Node *left = node->left; + Node *right = node->right; + + if (!right) + { + delete node; + return left; + } + + Node *replace = findReplacement(right); + replace->right = detachReplacement(right); + if (replace->right) + { + replace->right->parent = replace; + } + replace->left = left; + if (replace->left) + { + replace->left->parent = replace; + } + // на выходе из рекурсии редактируем parent + replace->parent = node->parent; + delete node; + + return doBalance(replace); + } + return doBalance(node); +} + +template +typename Set::Node *Set::findReplacement(Node *node) const +{ + if (!node) return nullptr; + while (node->left) + { + node = node->left; + } + return node; +} + +template +typename Set::Node *Set::detachReplacement(Node *node) +{ + if (!node) return nullptr; + if (!node->left) + { + return node->right; + } + node->left = detachReplacement(node->left); + return doBalance(node); +} + +template +typename Set::Node *Set::insertInternal(Node *node, const T &data) +{ + if (!node) + { + return new Node(data); + } + if (m_cmp(data, node->data)) + { + node->left = insertInternal(node->left, data); + // nullptr не возвращается, т.к. вставка обязательна + node->left->parent = node; + } + else if (m_cmp(node->data, data)) + { + node->right = insertInternal(node->right, data); + node->right->parent = node; + } + + return doBalance(node); +} + +template +size_t Set::getHeight(Node *node) const +{ + return node ? node->height : 0; +} + +template +void Set::fixHeight(Node *node) +{ + node->height = std::max(getHeight(node->left), getHeight(node->right)) + 1; +} + +template +typename Set::Node *Set::rotateLeft(Node *node) +{ + if (!node) return nullptr; + Node *tmp = node->right; + node->right = tmp->left; + if (tmp->left) + { + tmp->left->parent = node; + } + tmp->left = node; + tmp->parent = node->parent; + node->parent = tmp; + fixHeight(node); + fixHeight(tmp); + return tmp; +} + +template +typename Set::Node *Set::rotateRight(Node *node) +{ + if (!node) return nullptr; + Node *tmp = node->left; + node->left = tmp->right; + if (tmp->right) + { + tmp->right->parent = node; + } + tmp->right = node; + tmp->parent = node->parent; + node->parent = tmp; + + fixHeight(node); + fixHeight(tmp); + return tmp; +} + +template +int Set::getBalance(Node *node) const +{ + return getHeight(node->right) - getHeight(node->left); +} + +template +typename Set::Node *Set::doBalance(Node *node) +{ + if (!node) return nullptr; + fixHeight(node); + + switch (getBalance(node)) + { + case 2: { + if (getBalance(node->right) < 0) + { + node->right = rotateRight(node->right); + } + return rotateLeft(node); + } + case -2: { + if (getBalance(node->left) > 0) + { + node->left = rotateLeft(node->left); + } + return rotateRight(node); + } + default: { + return node; + } + } +} + +template +Iterator Set::begin() const +{ + if (!m_root) + { + return end(); + } + return Iterator(m_first, m_root); +} + +template +Iterator Set::end() const +{ + return Iterator(nullptr, m_root, true); +} + +template +typename Set::Node *Set::nextInternal(Node *node) const +{ + if (!node) return nullptr; + + if (node->right) + { + node = node->right; + return getLeftMost(node); + } + else + { + Node *needed_parent = node->parent; + while (needed_parent && needed_parent->left != node) + { + node = needed_parent; + needed_parent = node->parent; + } + return needed_parent; + } +} + +template +typename Set::Node *Set::prevInternal(Node *node) const +{ + if (!node) return nullptr; + if (node->left) + { + node = node->left; + return getRightMost(node); + } + else + { + Node *needed_parent = node->parent; + while (needed_parent && needed_parent->right != node) + { + node = needed_parent; + needed_parent = node->parent; + } + return needed_parent; + } +} + +template +std::size_t Set::size() const +{ + return m_size; +} + +template +bool Set::empty() const +{ + return m_size == 0; +} + +template +bool Set::operator==(Set const &other) const +{ + auto itOther = other.begin(); + for (auto it = begin(); it != end(); ++it) + { + if (*itOther != *it) + { + return false; + } + itOther++; + } + return true; +} + +template +bool Set::operator!=(Set const &other) const +{ + return !(*this == other); +} diff --git a/set_lib/src/set.cpp b/set_lib/src/set.cpp new file mode 100644 index 0000000..aa2f9c5 --- /dev/null +++ b/set_lib/src/set.cpp @@ -0,0 +1 @@ +#include "set.h" diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..29ecc96 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.0.0) +project(test_set) + +set(CMAKE_CXX_STANDARD 17) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage -O0") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -coverage -lgcov" ) + + +file(GLOB SOURCES *.cpp) + +find_package(GTest REQUIRED) + +add_executable(${PROJECT_NAME} ${SOURCES}) +target_include_directories(${PROJECT_NAME} PUBLIC ${SET_LIB_INCLUDE_DIRS}) + +target_link_libraries(${PROJECT_NAME} ${SET_LIB_LIBRARIES} GTest::GTest gtest_main) + +gtest_discover_tests(${PROJECT_NAME}) diff --git a/tests/test_cases_alexey_halaidzhy.cpp b/tests/test_cases_alexey_halaidzhy.cpp new file mode 100644 index 0000000..e77e0be --- /dev/null +++ b/tests/test_cases_alexey_halaidzhy.cpp @@ -0,0 +1,234 @@ +#include "iterator.h" +#include "set.h" +#include +#include +#include + +void fail(const char *message) +{ + std::cerr << "Fail:\n"; + std::cerr << message << "\n"; + std::cout << "-1 bad output\n"; // to get WA + exit(0); +} + +void check_constness() +{ + std::cerr << "check constness... "; + const Set s{-4, 5, 3, 0, 7}; + if (s.find(3) == s.end()) + fail("3 not found, incorrect find"); + if (*s.lower_bound(2) != 3 || s.lower_bound(8) != s.end() || *s.lower_bound(-2) != 0) + fail("incorrect lower_bound"); + if (s.empty()) + fail("incorrect empty"); + if (s.size() != 5) + fail("incorrect size"); + auto first = s.begin(); + Set::iterator last = s.end(); + if (*first != -4 || *(--last) != 7) + fail("incorrect begin or end"); + std::cerr << "ok!\n"; +} + +/* check if class correctly implements + * copy constructor and = operator */ +void check_copy_correctness() +{ + std::cerr << "check copy... "; + int elems[] = {3, 3, -1, 6, 0, 0, 17, -5, 4, 2}; + Set s1(elems, elems + 10); + std::set set_elems(elems, elems + 10); + Set s2 = s1; + s2.insert(5); + s2.insert(18); + s2.insert(-2); + auto s1_it = s1.begin(), s2_it = s2.begin(); + auto s_it = set_elems.begin(); + while (s1_it != s1.end() || s2_it != s2.end() || s_it != set_elems.end()) + { + if (*s2_it == 5 || *s2_it == 18 || *s2_it == -2) + { + ++s2_it; + continue; + } + if (*s1_it != *s2_it || *s1_it != *s_it || *s2_it != *s_it) + fail("fail after copy construction and insertions"); + ++s1_it, ++s2_it, ++s_it; + } + + s1 = s2; + s2.insert(19); + auto cur_end = s2.end(); + cur_end--; + s1_it = s1.begin(), s2_it = s2.begin(); + while (s1_it != s1.end() || s2_it != cur_end) + { + if (*s1_it != *s2_it) + fail("wrong = operator"); + ++s1_it, ++s2_it; + } + + s1 = s1 = s2; + s1_it = s1.begin(), s2_it = s2.begin(); + while (s1_it != s1.end() || s2_it != s2.end()) + { + if (*s1_it != *s2_it) + fail("wrong = operator"); + ++s1_it, ++s2_it; + } + std::cerr << "ok!\n"; +} + +/* check if class correctly handles empty set */ +void check_empty() +{ + std::cerr << "check empty set handling... "; + Set s; + if (!s.empty()) + fail("wrong empty"); + auto begin = s.begin(), end = s.end(); + if (begin != end) + fail("incorrect handling empty set"); + std::string elem("abacaba"); + s.insert(elem); + if (*s.lower_bound("aac") != elem) + fail("wrong lower_bound"); + Set empty; + Set s2{"opa"}; + s2 = empty; + if (s2.size()) + fail("incorrect size"); + Set s3(s2); + if (!s3.empty()) + fail("incorrect empty"); + std::cerr << "ok!\n"; +} + +/* check if class correctly works with iterators */ +void check_iterators() +{ + std::cerr << "check iterators... "; + Set> s{{-3, 5}, {5, 5}, {-4, 1}, {-4, 4}, {0, 1}, {3, 0}}; + if (s.begin()->second != 1 || (++s.begin())->first != -4) + fail("wrong begin()"); + Set>::iterator cur = s.end(); + Set small{1}; + Set::iterator it; + it = small.begin(); + if (*it != 1) + fail("incorrect begin"); + auto begin = s.begin(); + begin++; + cur--; + if (begin == cur) + fail("wrong == for iterators"); + while (begin != cur) + { + ++begin; + --cur; + } + if (*begin != *cur) + fail("wrong Iterators"); + std::cerr << "ok!\n"; +} + +struct StrangeInt +{ + int x; + static int counter; + StrangeInt() + { + ++counter; + } + StrangeInt(int x) : x(x) + { + ++counter; + } + StrangeInt(const StrangeInt &rs) : x(rs.x) + { + ++counter; + } + bool operator<(const StrangeInt &rs) const + { + return x < rs.x; + } + + static void init() + { + counter = 0; + } + + ~StrangeInt() + { + --counter; + } + + friend std::ostream &operator<<(std::ostream &out, const StrangeInt &x) + { + out << x.x; + return out; + } +}; +int StrangeInt::counter; + +/* check if class uses only < for elements comparing */ +void check_operator_less() +{ + std::cerr << "check operator <... "; + + Set s{-5, -3, -6, 13, 7, 1000, 963}; + auto it = s.lower_bound(999); + ++it; + if (it != s.end()) + fail("wrong ++ for iterator"); + std::cerr << "ok!\n"; +} + +/* check if class correctly implements destructor */ +void check_destructor() +{ + std::cerr << "check destructor... "; + StrangeInt::init(); + { + Set s{5, 4, 3, 2, 1, 0}; + if (s.size() != 6) + fail("wrong size"); + } + if (StrangeInt::counter) + fail("wrong destructor (or constructors)"); + { + Set s{-3, 3, -2, 2, -1, 1}; + Set s1(s); + s1.insert(0); + Set s2(s1); + if (s1.find(0) == s1.end()) + fail("wrong find"); + s1 = s; + if (s1.find(0) != s1.end()) + fail("wrong find"); + } + if (StrangeInt::counter) + fail("wrong destructor (or constructors)"); + std::cerr << "ok!\n"; +} + +/* check erase for correctness */ +void check_erase() +{ + std::cerr << "check erase... "; + Set s{"abacaba", "hello", "p"}; + s.erase("miss"); + s.erase("hello"); + if (s.size() != 2) + fail("Bad size"); + s.erase("p"); + if (*s.begin() != "abacaba") + fail("Bad begin"); + s.erase("1"); + s.erase("abacaba"); + s.erase("012"); + if (!s.empty()) + fail("Bad empty"); + std::cerr << "ok!\n"; +} diff --git a/tests/test_set.cpp b/tests/test_set.cpp new file mode 100644 index 0000000..b1d74b0 --- /dev/null +++ b/tests/test_set.cpp @@ -0,0 +1,252 @@ +#include "iterator.h" +#include "set.h" +#include +#include +#include +#include + +template +void TestInsertErase(Set &set, std::istream &in) +{ + char op; + T data; + while (in >> op >> data) + { + switch (op) + { + case '+': { + set.insert(data); + break; + } + case '-': { + set.erase(data); + break; + } + } + } +} + +TEST(TestBasicsFunctional, Test_Insert_Erase_Find_LowerBound) +{ + Set set; + EXPECT_EQ(set.size(), 0); + EXPECT_TRUE(set.empty()); + + // fill 1..9, -20, 20 + // remove odd + std::string strTest = "+ 1 + 1 + 2 + 2 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 - 2 - 4 - 6 - 8 -8 -8 + -20 + 20"; + std::istringstream stream(strTest); + TestInsertErase(set, stream); + + EXPECT_EQ(set.size(), 7); + EXPECT_TRUE(!set.empty()); + + EXPECT_EQ(*(set.find(1)), 1); + EXPECT_EQ(*(set.find(3)), 3); + EXPECT_EQ(*(set.find(5)), 5); + EXPECT_EQ(*(set.find(7)), 7); + EXPECT_EQ(*(set.find(9)), 9); + EXPECT_EQ(*(set.find(-20)), -20); + EXPECT_EQ(*(set.find(20)), 20); + + EXPECT_EQ(*(set.lower_bound(2)), 3); + EXPECT_EQ(*(set.lower_bound(9)), 9); + EXPECT_EQ(*(set.lower_bound(-100)), -20); + + // EXPECT_EQ: отсутствие оператора == ... + EXPECT_TRUE(set.find(2) == set.end()); + EXPECT_TRUE(set.find(4) == set.end()); + EXPECT_TRUE(set.find(6) == set.end()); + EXPECT_TRUE(set.find(8) == set.end()); + + set.erase(12345); + for (auto elem : {1, 3, 5, 7, 9, -20}) + { + set.erase(elem); + } + EXPECT_EQ(set.size(), 1); + set.erase(20); + EXPECT_EQ(set.size(), 0); + set.erase(20); + set.erase(2001); +} + +TEST(TestBasicsFunctional, Test_Increment_Decrement_Begin_End) +{ + Set set; + EXPECT_TRUE(set.begin() == set.end()); + set = {10, 20, 30, 40, 50, 60, 70, 80, 90}; + Set::iterator it = set.find(40); + EXPECT_TRUE(it != set.end()); + EXPECT_EQ(*it, 40); + EXPECT_EQ(*(--it), 30); + EXPECT_EQ(*(it++), 30); + EXPECT_EQ(*(it), 40); + EXPECT_EQ(*(++it), 50); + EXPECT_TRUE(it == set.find(50)); + EXPECT_TRUE(it != set.find(70)); + EXPECT_EQ(*set.begin(), 10); + EXPECT_EQ(*(++set.begin()), 20); + // РАБОТАЕТ!!! + EXPECT_EQ(*(--set.end()), 90); +} + +struct A +{ + int a; + int b; + bool operator<(A const &right) const + { + return a < right.a; + } +}; + +TEST(TestBasicsFunctional, Test_Before_Begin) +{ + Set set; + set = {{1, 2}, {3, 4}, {5, 6}}; + auto it = set.begin(); + it--; + EXPECT_THROW(*it, std::exception); + EXPECT_THROW(it->a, std::exception); + ++it; + EXPECT_EQ(it->b, 2); +} + +TEST(TestBasicsFunctional, Test_Strange) +{ + Set set; + auto it = set.end(); + ++it; + --it; + set.insert(55); + EXPECT_EQ(*(set.find(55)), 55); +} + +TEST(TestBasicsFunctional, Test_Iterator_default_ctor) +{ + Set set = {1, 2, 3}; + Set::iterator it; + it = set.begin(); + EXPECT_EQ(*it, 1); +} + +TEST(TestBasicsFunctional, Test_Insert_Erase_Random) +{ + Set set = {4, 6, 2}; + set.insert(1); + set.insert(3); + set.insert(45); + set.insert(13); + set.insert(78); + set.insert(-13); + set.insert(-345); + set.insert(-6); + set.erase(1); + set.erase(-13); + set.erase(-345); + set.erase(2); + set.erase(45); + set.insert(777); + set.erase(78); + set.erase(777); +} + +TEST(TestBasicsFunctional, Test_Ctor_CopyCtor_AssignOperator) +{ + Set set0 = {1, 2, 3, 4, 5}; + Set set1(set0); + Set set2(set1.begin(), set1.end()); + EXPECT_EQ(set1, set2); + set2.erase(4); + EXPECT_NE(set1, set2); + + set2 = set1; + EXPECT_EQ(set1, set2); + + auto it1 = set1.find(2); + auto it2 = set1.find(5); + Set set3(it1, it2); + EXPECT_EQ(set3.size(), 3); + auto it = set3.begin(); + for (auto elem : {2, 3, 4}) + { + EXPECT_EQ(*it, elem); + ++it; + } +} + +TEST(TestBasicsFunctional, Test_Compatibility_with_STL_Next_Advance_Prev) +{ + // for range выше + Set set = {1, 2, 3, 4, 5}; + Set::iterator it = set.begin(); + std::advance(it, 3); + EXPECT_EQ(*it, 4); + auto it2 = std::next(it); + EXPECT_EQ(*it2, 5); + it2 = std::prev(it, 2); + EXPECT_EQ(*it2, 2); +} + +TEST(TestBasicsFunctional, Test_Compatibility_with_STL_Vector) +{ + std::vector vec = {1, 2, 3, 4, 5}; + Set set(vec.begin(), vec.end()); + auto it = set.begin(); + for (auto &&elem : vec) + { + EXPECT_EQ(*it, elem); + ++it; + } + + int count = std::count_if(set.begin(), set.end(), [](int data) { return data % 2 == 0; }); + EXPECT_EQ(count, 2); + + // CORRECT ERROR: no matching function for call to ‘Set::insert(Set::iterator&, const value_type&)’ + + std::vector vec2; + std::copy(set.begin(), set.end(), std::back_inserter(vec2)); + size_t index = 0; + for (auto &&elem : set) + { + EXPECT_EQ(elem, vec2[index]); + index++; + } + + /* + + CORRECT ERROR: no matching function for call to ‘Set::insert(Set::iterator&, const value_type&)’ + Set set2 = {-2, -1, 0, 6}; + std::copy(set.begin(), set.end(), std::inserter(set2, set2.begin())); + */ + /* + нельзя, т.к. итератор константный + auto square = [](int &data) + { + data * data; + }; + std::for_each(set.begin(), set.end(), square); + + */ +} + +extern void check_constness(); +extern void check_copy_correctness(); +extern void check_empty(); +extern void check_iterators(); +extern void check_operator_less(); +extern void check_destructor(); +extern void check_erase(); + +TEST(TestOther, Test_Alexey_Halaidzhy) +{ + // в тест кейсах exit(0) при ошибках, поэтому фейл будет отображен + check_constness(); + check_copy_correctness(); + check_empty(); + check_iterators(); + check_operator_less(); + check_destructor(); + check_erase(); +}