commit 402f860bbc81ef5e623eee14e7267b78e502c901 Author: Maximilian Keßler Date: Sat Apr 16 09:24:00 2022 +0200 initial commit: Working version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2968585 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.out +mybenchmark +cmake-build-debug/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9c27ab1 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.22) +project(prog) + +set(CMAKE_CXX_STANDARD 20) + +find_package(benchmark REQUIRED) + +add_executable(prog main.cpp segment_tree.h geometry.h) + +target_link_libraries(prog benchmark::benchmark) \ No newline at end of file diff --git a/geometry.h b/geometry.h new file mode 100644 index 0000000..0c2879b --- /dev/null +++ b/geometry.h @@ -0,0 +1,24 @@ +// +// Created by maximilian on 16.04.22. +// + +#ifndef PROG_GEOMETRY_H +#define PROG_GEOMETRY_H + +#include + +using Coordinate = int; +using Unit = uint64_t; +using Index = size_t; + +struct Point { + Coordinate x; + Coordinate y; +}; + +struct Interval { + Coordinate left; + Coordinate right; +}; + +#endif //PROG_GEOMETRY_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..e19d361 --- /dev/null +++ b/main.cpp @@ -0,0 +1,135 @@ +#include +#include + +#include + +#include "geometry.h" + +struct Rectangle { + Point bottom_left; + Point top_right; + + // only for algorithm + Index i_left; + Index i_right; +}; + +struct RectCoord { + Coordinate coord; + Rectangle* rect; // guaranteed to be valid at all times + bool operator<(const RectCoord &other) { + return coord < other.coord; + } +}; + +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 diff --git a/segment_tree.h b/segment_tree.h new file mode 100644 index 0000000..ed24f8b --- /dev/null +++ b/segment_tree.h @@ -0,0 +1,21 @@ +// +// Created by maximilian on 16.04.22. +// + +#include +#include "geometry.h" + +#ifndef PROG_SEGMENT_TREE_H +#define PROG_SEGMENT_TREE_H + + +class SegmentTree { + SegmentTree(const std::vector); + + Unit length_covered_intervals(); + + void add_interval(Interval interval); + void remove_interval(Interval interval); +}; + +#endif //PROG_SEGMENT_TREE_H