diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..41d22f1 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.5 FATAL_ERROR) + +project(heaps CXX) +set(CMAKE_CXX_STANDARD 17) + +include(GNUInstallDirs) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}) + +# we use this to get code coverage +if(CMAKE_CXX_COMPILER_ID MATCHES GNU) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") +endif() + +add_subdirectory(src/BinaryHeap) + +include(cmake/googletest.cmake) +fetch_googletest( + ${PROJECT_SOURCE_DIR}/cmake + ${PROJECT_BINARY_DIR}/googletest +) + +enable_testing() +add_subdirectory(test) + diff --git a/cmake/googletest-download.cmake b/cmake/googletest-download.cmake new file mode 100644 index 0000000..f580295 --- /dev/null +++ b/cmake/googletest-download.cmake @@ -0,0 +1,20 @@ +# code copied from https://crascit.com/2015/07/25/cmake-gtest/ +cmake_minimum_required(VERSION 3.5 FATAL_ERROR) + +project(googletest-download NONE) + +include(ExternalProject) + +ExternalProject_Add( + googletest + SOURCE_DIR "@GOOGLETEST_DOWNLOAD_ROOT@/googletest-src" + BINARY_DIR "@GOOGLETEST_DOWNLOAD_ROOT@/googletest-build" + GIT_REPOSITORY + https://github.com/google/googletest.git + GIT_TAG + release-1.8.0 + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) diff --git a/cmake/googletest.cmake b/cmake/googletest.cmake new file mode 100644 index 0000000..de0c3bb --- /dev/null +++ b/cmake/googletest.cmake @@ -0,0 +1,32 @@ +# the following code to fetch googletest +# is inspired by and adapted after https://crascit.com/2015/07/25/cmake-gtest/ +# download and unpack googletest at configure time + +macro(fetch_googletest _download_module_path _download_root) + set(GOOGLETEST_DOWNLOAD_ROOT ${_download_root}) + configure_file( + ${_download_module_path}/googletest-download.cmake + ${_download_root}/CMakeLists.txt + @ONLY + ) + unset(GOOGLETEST_DOWNLOAD_ROOT) + + execute_process( + COMMAND + "${CMAKE_COMMAND}" -G "${CMAKE_GENERATOR}" . + WORKING_DIRECTORY + ${_download_root} + ) + execute_process( + COMMAND + "${CMAKE_COMMAND}" --build . + WORKING_DIRECTORY + ${_download_root} + ) + + # adds the targers: gtest, gtest_main, gmock, gmock_main + add_subdirectory( + ${_download_root}/googletest-src + ${_download_root}/googletest-build + ) +endmacro() diff --git a/src/BinaryHeap/BinaryHeap.hpp b/src/BinaryHeap/BinaryHeap.hpp new file mode 100644 index 0000000..b50c24f --- /dev/null +++ b/src/BinaryHeap/BinaryHeap.hpp @@ -0,0 +1,301 @@ +// +// Created by not sashka on 19.10.18. +// + +#ifndef BINARY_HEAP_BINARYHEAP_H +#define BINARY_HEAP_BINARYHEAP_H + +#include +#include +#include +#include +#include +#include +#include "Vector.hpp" + +template +class BinaryHeap { + private: + class Pointer; + public: + bool empty(); + + size_t size(); + + size_t get_tree_degree(); + + std::shared_ptr insert(T value); + + T min(); + + T extract_min(); + + T extract(std::shared_ptr pointer); + + void change(std::shared_ptr pointer, T new_value); + + BinaryHeap(); + + template + BinaryHeap(Iterator begin, Iterator end); + + void optimize(size_t insert_count, size_t extract_count) { + if (size() != 0) { + throw std::runtime_error("Can Not optimize non empty heap"); + } + if (extract_count > insert_count) { + throw std::invalid_argument("Extract count can not be grater than insert count"); + } + if (extract_count == 0) { + throw std::invalid_argument("Extract count can not be 0"); + } + + double alpha = 1.0 * insert_count / extract_count; + double prev_time_consumption = -1; + double time_consumption = -1; + unsigned long k = 1; + + while (prev_time_consumption == -1 || time_consumption <= prev_time_consumption) { + ++k; + prev_time_consumption = time_consumption; + time_consumption = (1.0 * k + alpha) / std::log(k); + } + + tree_degree = k - 1; + } + + private: + class Pointer { + public: + friend class BinaryHeap; + + Pointer(unsigned long index, BinaryHeap *parent_heap); + + T value(); + + bool is_valid(); + + BinaryHeap *get_parent_heap(); + + private: + BinaryHeap *parent_heap; + unsigned long array_index; + bool valid; + }; + + struct Element { + T value; + std::shared_ptr::Pointer> pointer; + }; + + size_t tree_degree = 2; + Vector storage; + + void swap_elements(unsigned long first_index, unsigned long second_index); + + unsigned long parent(unsigned long index); + + unsigned long nth_child(unsigned long index, unsigned long n); + + void sift_up(unsigned long index); + + void sift_down(unsigned long index); + + std::shared_ptr push_back_element(T value); +}; + +template +BinaryHeap::BinaryHeap() = default; + +template +template +BinaryHeap::BinaryHeap(Iterator begin, Iterator end) { + for (Iterator iter = begin; iter != end; ++iter) { + push_back_element(*iter); + } + + //call sift_down for all non leaves + for (unsigned long i = size() / 2; i > 0; --i) { + sift_down(i - 1); + } +} + +template +size_t BinaryHeap::size() { + return storage.size(); +} + +template +bool BinaryHeap::empty() { + return storage.empty(); +} + +template +size_t BinaryHeap::get_tree_degree() { + return tree_degree; +} + +template +std::shared_ptr::Pointer> BinaryHeap::insert(T value) { + auto pointer = push_back_element(value); + sift_up(pointer->array_index); + return pointer; +} + +template +T BinaryHeap::min() { + if (empty()) { + throw std::runtime_error("No minimal element. Heap is empty."); + } + return storage[0].value; +} + +template +T BinaryHeap::extract(std::shared_ptr pointer) { + if (!pointer->is_valid()) { + throw std::runtime_error("Invalidated pointer to an element"); + } + if (pointer->get_parent_heap() != this) { + throw std::runtime_error("Pointer from another heap"); + } + + unsigned long extract_index = pointer->array_index; + T value = pointer->value(); + + //try to swap extracted with last element and sift it down + if (extract_index == size() - 1) { + storage.pop_back(); + } else { + swap_elements(extract_index, size() - 1); + storage.pop_back(); + sift_down(extract_index); + } + + pointer->valid = false; + return value; +} + +template +T BinaryHeap::extract_min() { + if (empty()) { + throw std::runtime_error("No minimal element. Heap is empty."); + } + return extract(storage[0].pointer); +} + +template +void BinaryHeap::change(std::shared_ptr pointer, T new_value) { + if (!pointer->is_valid()) { + throw std::runtime_error("Invalidated pointer to an element"); + } + if (pointer->get_parent_heap() != this) { + throw std::runtime_error("Pointer from another heap"); + } + + T previous_value = pointer->value(); + storage[pointer->array_index].value = new_value; + + if (new_value > previous_value) { + sift_down(pointer->array_index); + } else { + sift_up(pointer->array_index); + } +} + +template +void BinaryHeap::swap_elements(unsigned long first_index, unsigned long second_index) { + if (first_index >= size() || second_index >= size()) { + throw std::out_of_range("At least one of indexes is out of range"); + } + std::swap(storage[first_index].value, storage[second_index].value); + std::swap(storage[first_index].pointer->array_index, storage[second_index].pointer->array_index); + std::swap(storage[first_index].pointer, storage[second_index].pointer); +} + +template +std::shared_ptr::Pointer> BinaryHeap::push_back_element(T value) { + unsigned long new_index = storage.size(); + std::shared_ptr pointer(new Pointer(new_index, this)); + storage.push_back({value, pointer}); + + return pointer; +} + +template +void BinaryHeap::sift_down(unsigned long index) { + if (index >= size()) { + throw std::out_of_range("Index is out of range"); + } + unsigned long min_index = index; + + for (unsigned long i = 0; i < tree_degree; ++i) { + unsigned long child_index = nth_child(index, i); + if (child_index < size() && storage[child_index].value < storage[min_index].value) { + min_index = child_index; + } + } + + if (min_index != index) { + swap_elements(min_index, index); + sift_down(min_index); + } +} + +template +void BinaryHeap::sift_up(unsigned long index) { + if (index >= size()) { + throw std::out_of_range("Index is out of range"); + } + unsigned long current = index; + + while (current != 0) { + unsigned long current_parent = parent(current); + if (storage[current].value < storage[current_parent].value) { + swap_elements(current, current_parent); + } + current = current_parent; + } +} + +template +unsigned long BinaryHeap::nth_child(unsigned long index, unsigned long n) { + if (index >= size()) { + throw std::out_of_range("Index is out of range"); + } + return index * tree_degree + n + 1; +} + +template +unsigned long BinaryHeap::parent(unsigned long index) { + if (index == 0) { + throw std::runtime_error("Root node has no parent"); + } + if (index >= size()) { + throw std::out_of_range("Index is out of range"); + } + return (index - 1) / tree_degree; +} + +template +bool BinaryHeap::Pointer::is_valid() { + return valid; +} + +template +T BinaryHeap::Pointer::value() { + return parent_heap->storage[array_index].value; +} + +template +BinaryHeap::Pointer::Pointer(unsigned long index, BinaryHeap *const parent_heap) { + this->array_index = index; + this->parent_heap = parent_heap; + this->valid = true; +} + +template +BinaryHeap *BinaryHeap::Pointer::get_parent_heap() { + return parent_heap; +} + +#endif //BINARY_HEAP_BINARYHEAP_H diff --git a/src/BinaryHeap/CMakeLists.txt b/src/BinaryHeap/CMakeLists.txt new file mode 100644 index 0000000..34d4f6f --- /dev/null +++ b/src/BinaryHeap/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(binaryHeap) + +target_sources( + binaryHeap + PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/BinaryHeap.hpp + ${CMAKE_CURRENT_LIST_DIR}/Vector.hpp +) + +target_include_directories( + binaryHeap + PUBLIC + ${CMAKE_CURRENT_LIST_DIR} +) + +set_target_properties(binaryHeap PROPERTIES LINKER_LANGUAGE CXX) \ No newline at end of file diff --git a/src/BinaryHeap/Vector.hpp b/src/BinaryHeap/Vector.hpp new file mode 100644 index 0000000..867b950 --- /dev/null +++ b/src/BinaryHeap/Vector.hpp @@ -0,0 +1,75 @@ +// +// Created by runfme on 13.11.18. +// +#ifndef VECTOR_H +#define VECTOR_H + +#include +template +class Vector { + public: + Vector() { + storage_size = 1; + size_ = 0; + storage = new T[storage_size]; + } + + void push_back(T value) { + storage[size_] = value; + ++size_; + adjust_storage(); + } + + void pop_back() { + --size_; + adjust_storage(); + } + + unsigned long size() { + return size_; + } + + bool empty() { + return size_ == 0; + } + + T& operator[](unsigned long index) { + if (index >= size_) { + throw std::out_of_range ("Index is out of range"); + } + return storage[index]; + } + + unsigned long get_storage_size() { + return storage_size; + } + + ~Vector() { + delete[] storage; + } + + private: + T* storage; + unsigned long storage_size; + unsigned long size_; + + void set_new_storage(unsigned long new_size) { + T* new_storage = new T[storage_size * 2]; + for (int i = 0; i < size_; ++i) { + new_storage[i] = storage[i]; + } + delete[] storage; + storage = new_storage; + storage_size = new_size; + } + + void adjust_storage() { + if (size_ >= storage_size) { + set_new_storage(storage_size * 2); + } else if (size_ != 0 && size_ * 4 <= storage_size) { + set_new_storage(storage_size / 2); + } + } +}; + +#endif \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..ee9b79d --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.9) + +add_executable( + unit_tests + binary_heap_test.cpp + vector_test.cpp + main.cpp) + +target_link_libraries( + unit_tests + gtest_main + binaryHeap +) + +add_test( + NAME + unit + COMMAND + ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}/unit_tests +) diff --git a/test/binary_heap_test.cpp b/test/binary_heap_test.cpp new file mode 100644 index 0000000..7eb94be --- /dev/null +++ b/test/binary_heap_test.cpp @@ -0,0 +1,179 @@ +// +// Created by runfme on 16.11.18. +// + + +#define BOOST_TEST_MAIN +#define BOOST_TEST_MODULE testBinaryHeap + +#include "../src/BinaryHeap/BinaryHeap.hpp" +#include + +TEST(BinaryHeapBasics, Constructor) { + BinaryHeap heap = BinaryHeap(); + EXPECT_EQ(heap.empty(), true); +} + +TEST(BinaryHeapBasics, Insert) { + BinaryHeap heap = BinaryHeap(); + auto pointer = heap.insert(1); + EXPECT_EQ(pointer->value(), 1); +} + +TEST(BinaryHeapBasics, Empty) { + BinaryHeap heap = BinaryHeap(); + EXPECT_EQ(heap.empty(), true); + + heap.insert(1); + EXPECT_EQ(heap.empty(), false); +} + +TEST(BinaryHeapBasics, GetMin) { + BinaryHeap heap = BinaryHeap(); + heap.insert(2); + heap.insert(3); + heap.insert(1); + heap.insert(4); + EXPECT_EQ(heap.min(), 1); +} + +TEST(BinaryHeapBasics, ExtractMin) { + BinaryHeap heap = BinaryHeap(); + heap.insert(2); + heap.insert(3); + EXPECT_EQ(heap.extract_min(), 2); + heap.insert(1); + EXPECT_EQ(heap.extract_min(), 1); +} + +TEST(BinaryHeapBasics, Size) { + BinaryHeap heap = BinaryHeap(); + EXPECT_EQ(heap.size(), 0); + heap.insert(1); + EXPECT_EQ(heap.size(), 1); + heap.insert(2); + heap.insert(6); + heap.insert(1); + EXPECT_EQ(heap.size(), 4); + heap.extract_min(); + heap.extract_min(); + EXPECT_EQ(heap.size(), 2); +} + +TEST(BinaryHeapAdvanced, Delete) { + BinaryHeap heap = BinaryHeap(); + auto pointer_to_1 = heap.insert(1); + EXPECT_EQ(heap.extract(pointer_to_1), 1); + EXPECT_EQ(pointer_to_1->is_valid(), false); + + heap.insert(3); + auto pointer_to_2 = heap.insert(2); + heap.insert(1); + EXPECT_EQ(heap.extract(pointer_to_2), 2); + EXPECT_EQ(pointer_to_2->is_valid(), false); +} + +TEST(BinaryHeapAdvanced, Change) { + BinaryHeap heap = BinaryHeap(); + heap.insert(1); + heap.insert(3); + auto pointer_to_2 = heap.insert(2); + heap.insert(1); + + heap.change(pointer_to_2, 0); + EXPECT_EQ(pointer_to_2->value(), 0); + EXPECT_EQ(heap.min(), 0); +} + +TEST(BinaryHeapAdvanced, IteratorConstructor) { + int a[] = {4,2,5,1,6,3}; + BinaryHeap heap = BinaryHeap(a, a+6); + EXPECT_EQ(heap.extract_min(), 1); + EXPECT_EQ(heap.extract_min(), 2); + EXPECT_EQ(heap.extract_min(), 3); + EXPECT_EQ(heap.extract_min(), 4); + EXPECT_EQ(heap.extract_min(), 5); + EXPECT_EQ(heap.extract_min(), 6); +} + +TEST(BinaryHeapAdvanced, EmptyIteratorConstructor) { + int a[] = {4}; + BinaryHeap heap = BinaryHeap(a, a+0); + EXPECT_EQ(heap.empty(), true); +} + +TEST(BinaryHeapAdvanced, Optimize) { + BinaryHeap heap = BinaryHeap(); + + heap.optimize(1, 1); + EXPECT_EQ(heap.get_tree_degree(), 4); + + heap.insert(2); + heap.insert(3); + heap.insert(3); + heap.insert(63); + heap.insert(-1); + heap.insert(0); + heap.insert(7); + EXPECT_EQ(heap.extract_min(), -1); + heap.insert(1); + EXPECT_EQ(heap.extract_min(), 0); +} + +TEST(BinaryHeapExceptions, RequestsToEmptyHeap) { + BinaryHeap heap = BinaryHeap(); + + EXPECT_THROW(heap.min(), std::runtime_error); + EXPECT_THROW(heap.extract_min(), std::runtime_error); +} + +TEST(BinaryHeapExceptions, RequestsToInvalidatedPointer) { + BinaryHeap heap = BinaryHeap(); + auto pointer_to_1 = heap.insert(1); + heap.extract(pointer_to_1); + + EXPECT_THROW(heap.extract(pointer_to_1), std::runtime_error); + EXPECT_THROW(heap.change(pointer_to_1, 10), std::runtime_error); +} + +TEST(BinaryHeapExceptions, RequestsToPointerFromAnotherHeap) { + BinaryHeap heap1 = BinaryHeap(); + BinaryHeap heap2 = BinaryHeap(); + auto pointer_to_1 = heap1.insert(1); + + EXPECT_THROW(heap2.change(pointer_to_1, 10), std::runtime_error); +} + + +TEST(BinaryHeapExceptions, Stress) { + BinaryHeap heap = BinaryHeap(); + int size = 0; + for (int i = 0; i < 10; ++i) { + for (int j = 0; j < 5000; ++j) { + auto a = heap.insert(j); + size++; + EXPECT_EQ(heap.size(), size); + heap.change(a, 2500); + EXPECT_EQ(a->value(), 2500); + heap.change(a, j); + EXPECT_EQ(a->value(), j); + heap.change(a, rand()); + } + for (int k = 0; k < 2000; ++k) { + heap.extract_min(); + + size--; + EXPECT_EQ(heap.size(), size); + } + } +} + +TEST(BinaryHeapExceptions, OptimizeExceptions) { + BinaryHeap heap = BinaryHeap(); + heap.insert(1); + + EXPECT_THROW(heap.optimize(1, 1), std::runtime_error); + + heap.extract_min(); + EXPECT_THROW(heap.optimize(10, 100), std::invalid_argument); +} \ No newline at end of file diff --git a/test/main.cpp b/test/main.cpp new file mode 100644 index 0000000..5b08882 --- /dev/null +++ b/test/main.cpp @@ -0,0 +1,11 @@ +// +// Created by sashka on 19.10.18. +// + +#include "gtest/gtest.h" + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/test/vector_test.cpp b/test/vector_test.cpp new file mode 100644 index 0000000..d2a0e45 --- /dev/null +++ b/test/vector_test.cpp @@ -0,0 +1,71 @@ +// +// Created by runfme on 16.11.18. +// + +#define BOOST_TEST_MAIN +#define BOOST_TEST_MODULE testVector + +#include "../src/BinaryHeap/Vector.hpp" +#include + +TEST(VectorBasics, Constuctor) { + Vector array; + EXPECT_EQ(array.empty(), true); +} + +TEST(VectorBasics, PushBack) { + Vector array; + array.push_back(1); + EXPECT_EQ(array[0], 1); + array.push_back(2); + EXPECT_EQ(array[1], 2); +} + +TEST(VectorBasics, ElementAcess) { + Vector array; + array.push_back(1); + EXPECT_EQ(array[0], 1); + array[0] = 2; + EXPECT_EQ(array[0], 2); +} + +TEST(VectorBasics, Size) { + Vector array; + EXPECT_EQ(array.size(), 0); + array.push_back(1); + EXPECT_EQ(array.size(), 1); + array.push_back(2); + array.push_back(3); + array.push_back(4); + EXPECT_EQ(array.size(), 4); +} + +TEST(VectorBasics, PopBack) { + Vector array; + array.push_back(1); + array.push_back(2); + array.push_back(3); + array.push_back(4); + array.pop_back(); + EXPECT_EQ(array.size(), 3); +} + +TEST(VectorBasics, StorageResize) { + Vector array; + EXPECT_EQ(array.get_storage_size(), 1); + array.push_back(1); + EXPECT_EQ(array.get_storage_size(), 2); + array.push_back(2); + array.push_back(3); + array.push_back(4); + EXPECT_EQ(array.get_storage_size(), 8); + array.pop_back(); + array.pop_back(); + EXPECT_EQ(array.get_storage_size(), 4); +} + +TEST(VectorBasics, OutOfRangeAccess) { + Vector array; + array.push_back(1); + EXPECT_THROW(array[10], std::out_of_range); +} \ No newline at end of file