diff --git a/.gitignore b/.gitignore index 2968585..8a132e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ *.out mybenchmark cmake-build-debug/ +Makefile +cmake_install.cmake +CMakeFiles/ +CMakeCache.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 480f5b3..630f894 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,8 @@ set(CMAKE_CXX_STANDARD 20) find_package(benchmark REQUIRED) -add_executable(prog main.cpp segment_tree.h geometry.h segment_tree.cpp) +add_executable(benchmark.out segment_tree.h segment_tree.cpp geometry.h areas.cpp areas.h benchmark.cpp) -target_link_libraries(prog benchmark::benchmark) \ No newline at end of file +add_executable(main.out segment_tree.h segment_tree.cpp geometry.h areas.h areas.cpp main.cpp) + +target_link_libraries(benchmark.out benchmark::benchmark) \ No newline at end of file diff --git a/areas.cpp b/areas.cpp new file mode 100644 index 0000000..5c47ac5 --- /dev/null +++ b/areas.cpp @@ -0,0 +1,103 @@ +// +// Created by maximilian on 16.04.22. +// + +#include +#include + +#include "geometry.h" +#include "areas.h" + + +Unit get_area_union(std::vector rectangles) { + // do some input sanity checks + for(const auto &rect : rectangles) { + assert(rect.bottom_left.x <= rect.top_right.x); + assert(rect.bottom_left.y <= rect.top_right.y); + } + // entry i will describe coverage of the interval (x_points[i], x_points[i+1]) + std::vector coverage_numbers; + coverage_numbers.reserve(2*rectangles.size() -1); + + //sorted vector of all x coordinates of all rectangles + std::vector x_points; + x_points.reserve(2* rectangles.size()); + for(auto &rect : rectangles) { + x_points.push_back({rect.bottom_left.x, &rect}); + x_points.push_back({rect.top_right.x, &rect}); + } + std::sort(x_points.begin(), x_points.end()); + + // preprocessing for constant rectangle lookup during algorithm + // technically not necessary to achieve quadratic running time + for(Index i = 0 ; i < x_points.size() ; ++i) { + if(x_points[i].rect->bottom_left.x == x_points[i].coord) { + x_points[i].rect->i_left = i; + } else { + assert(x_points[i].rect->top_right.x == x_points[i].coord); + x_points[i].rect->i_right = i; + } + } + + // prepare vector of y-coordinates + std::vector y_points; + y_points.reserve(2*rectangles.size()); + for(auto &rect : rectangles) { + y_points.push_back({rect.bottom_left.y, &rect}); + y_points.push_back({rect.top_right.y, &rect}); + } + std::sort(y_points.begin(), y_points.end()); + + // run sweepline + Unit current_cross_section_length = 0; + Unit total_area = 0; + for(Index y_index = 0 ; y_index < y_points.size() ; ++y_index) { + // first, update cross section + if (y_points[y_index].rect->bottom_left.y == y_points[y_index].coord) { + // rectangle is starting now, add its cross section + for (Index index = y_points[y_index].rect->i_left; index < y_points[y_index].rect->i_right; ++index) { + ++coverage_numbers[index]; + if (coverage_numbers[index] == 1) { + current_cross_section_length += (x_points[index + 1].coord - x_points[index].coord); + } + } + } else { + assert(y_points[y_index].rect->top_right.y == y_points[y_index].coord); + // rectangle stopping, remove its cross section + for (Index index = y_points[y_index].rect->i_left; index < y_points[y_index].rect->i_right; ++index) { + --coverage_numbers[index]; + if (coverage_numbers[index] == 0) { + current_cross_section_length -= (x_points[index + 1].coord - x_points[index].coord); + } + } + } + //cross section is now up to date + if(y_index + 1 < y_points.size()) { + // add area up to next y point + total_area += current_cross_section_length * (y_points[y_index+1].coord - y_points[y_index].coord); + } + } + // sanity check that nothing is covered when arriving at the end + for(auto coverage : coverage_numbers) { + assert(coverage == 0); + } + return total_area; +} + +std::vector get_random_instance(unsigned num_rects, Coordinate range) { + std::vector rects; + rects.reserve(num_rects); + for(unsigned i = 0 ; i < num_rects; ++i) { + // technically, this might not be (perfectly) evenly distributed, but it will suffice for our purposes anyways + Coordinate x1 = rand() % range; + Coordinate x2 = rand() % range; + Coordinate y1 = rand() % range; + Coordinate y2 = rand() % range; + if (x2 < x1) { int tmp = x1; x1 = x2; x2 = tmp; }; + if (y2 < y1) { int tmp = y1; y1 = y2; y2 = tmp; }; + rects.push_back( + {{x1,y1},{x2, y2}, 0,0} + ); + } + return rects; +} diff --git a/areas.h b/areas.h new file mode 100644 index 0000000..dcb4fda --- /dev/null +++ b/areas.h @@ -0,0 +1,14 @@ +// +// Created by maximilian on 16.04.22. +// +#include +#include "geometry.h" + +#ifndef PROG_AREAS_HPP +#define PROG_AREAS_HPP + +Unit get_area_union(std::vector rectangles); + +std::vector get_random_instance(unsigned num_rects, Coordinate range); + +#endif //PROG_AREAS_HPP diff --git a/benchmark.cpp b/benchmark.cpp new file mode 100644 index 0000000..6ebd15d --- /dev/null +++ b/benchmark.cpp @@ -0,0 +1,24 @@ +// +// Created by maximilian on 16.04.22. +// + +#include + +#include "areas.h" + +static void BM_area_computation(benchmark::State& state) { + std::vector rects; + for (auto _ : state) { + state.PauseTiming(); + rects = get_random_instance(state.range(0), state.range(1)); + state.ResumeTiming(); + get_area_union(rects); + } + state.SetComplexityN(state.range(0)); +} + +// add benchmark +BENCHMARK(BM_area_computation)->ArgsProduct({benchmark::CreateRange(1,1<<16,4),{1<<10, 1<<15, 1<<20}})->Complexity(benchmark::oNSquared); + +// run benchmark as main() +BENCHMARK_MAIN(); diff --git a/main.cpp b/main.cpp index 8153a20..328a890 100644 --- a/main.cpp +++ b/main.cpp @@ -1,119 +1,5 @@ -#include -#include - -#include - -#include "geometry.h" -Unit get_area_union(std::vector rectangles) { - // do some input sanity checks - for(const auto &rect : rectangles) { - assert(rect.bottom_left.x <= rect.top_right.x); - assert(rect.bottom_left.y <= rect.top_right.y); - } - // entry i will describe coverage of the interval (x_points[i], x_points[i+1]) - std::vector coverage_numbers; - coverage_numbers.reserve(2*rectangles.size() -1); - - //sorted vector of all x coordinates of all rectangles - std::vector x_points; - x_points.reserve(2* rectangles.size()); - for(auto &rect : rectangles) { - x_points.push_back({rect.bottom_left.x, &rect}); - x_points.push_back({rect.top_right.x, &rect}); - } - std::sort(x_points.begin(), x_points.end()); - - // preprocessing for constant rectangle lookup during algorithm - // technically not necessary to achieve quadratic running time - for(Index i = 0 ; i < x_points.size() ; ++i) { - if(x_points[i].rect->bottom_left.x == x_points[i].coord) { - x_points[i].rect->i_left = i; - } else { - assert(x_points[i].rect->top_right.x == x_points[i].coord); - x_points[i].rect->i_right = i; - } - } - - // prepare vector of y-coordinates - std::vector y_points; - y_points.reserve(2*rectangles.size()); - for(auto &rect : rectangles) { - y_points.push_back({rect.bottom_left.y, &rect}); - y_points.push_back({rect.top_right.y, &rect}); - } - std::sort(y_points.begin(), y_points.end()); - - // run sweepline - Unit current_cross_section_length = 0; - Unit total_area = 0; - for(Index y_index = 0 ; y_index < y_points.size() ; ++y_index) { - // first, update cross section - if (y_points[y_index].rect->bottom_left.y == y_points[y_index].coord) { - // rectangle is starting now, add its cross section - for (Index index = y_points[y_index].rect->i_left; index < y_points[y_index].rect->i_right; ++index) { - ++coverage_numbers[index]; - if (coverage_numbers[index] == 1) { - current_cross_section_length += (x_points[index + 1].coord - x_points[index].coord); - } - } - } else { - assert(y_points[y_index].rect->top_right.y == y_points[y_index].coord); - // rectangle stopping, remove its cross section - for (Index index = y_points[y_index].rect->i_left; index < y_points[y_index].rect->i_right; ++index) { - --coverage_numbers[index]; - if (coverage_numbers[index] == 0) { - current_cross_section_length -= (x_points[index + 1].coord - x_points[index].coord); - } - } - } - //cross section is now up to date - if(y_index + 1 < y_points.size()) { - // add area up to next y point - total_area += current_cross_section_length * (y_points[y_index+1].coord - y_points[y_index].coord); - } - } - // sanity check that nothing is covered when arriving at the end - for(auto coverage : coverage_numbers) { - assert(coverage == 0); - } - return total_area; -} - -// some benchmark tests using github.com/google/benchmark - -std::vector get_random_instance(unsigned num_rects, Coordinate range) { - std::vector rects; - rects.reserve(num_rects); - for(unsigned i = 0 ; i < num_rects; ++i) { - // technically, this might not be (perfectly) evenly distributed, but it will suffice for our purposes anyways - Coordinate x1 = rand() % range; - Coordinate x2 = rand() % range; - Coordinate y1 = rand() % range; - Coordinate y2 = rand() % range; - if (x2 < x1) { int tmp = x1; x1 = x2; x2 = tmp; }; - if (y2 < y1) { int tmp = y1; y1 = y2; y2 = tmp; }; - rects.push_back( - {{x1,y1},{x2, y2}, 0,0} - ); - } - return rects; -} - -static void BM_area_computation(benchmark::State& state) { - std::vector rects; - for (auto _ : state) { - state.PauseTiming(); - rects = get_random_instance(state.range(0), state.range(1)); - state.ResumeTiming(); - get_area_union(rects); - } - state.SetComplexityN(state.range(0)); -} - -// add benchmark -BENCHMARK(BM_area_computation)->ArgsProduct({benchmark::CreateRange(1,1<<16,4),{1<<10, 1<<15, 1<<20}})->Complexity(benchmark::oNSquared); - -// run benchmark as main() -BENCHMARK_MAIN(); \ No newline at end of file +int main() { + return 0; +} \ No newline at end of file