Advertisement
Guest User

Untitled

a guest
Oct 17th, 2024
420
0
6 days
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 14.78 KB | None | 0 0
  1. #include <cassert>
  2. #include <cmath>
  3. #include <optional>
  4. #include <iostream>
  5. #include <vector>
  6.  
  7. /*
  8.  
  9. Context
  10. =======
  11.  
  12. This question is based on a real problem that we had to solve in production. There was a computationally
  13. expensive algorithm implemented for generic polygons; however we have noticed that in the majority of
  14. cases the polygon that we were passing to the algorithm was a rectangle, and for rectangles the
  15. implementation of the algorithm was much faster.
  16.  
  17. Task
  18. ====
  19.  
  20. Implement a function that determines whether a given `Polygon` represents an axis-aligned rectangle, and
  21. if so, returns a `Rectangle` which is equivalent.
  22.  
  23. You may assume:
  24.  
  25. - `Polygon` is a counter-clockwise oriented collection of points. This means the inside of the polygon
  26.    would be on your left if you walked around the boundary following the points in the order provided
  27. - `Polygon`, if not empty, is guaranteed to be cyclic, that is `polygon.front() == polygon.back()`
  28. - `Polygon`, is not self-intersecting. This means that the edges between pairs of adjacent points in
  29.   the polygon do not cross.
  30.  
  31. The `Polygon` may have been generated by other algorithms which suffer numerical instability problems so
  32. your implementation should aim to be robust to degenerate cases. Some examples of degenerate cases that
  33. your solution should handle are:
  34.  
  35. - Points lying on the straight edge connecting another pair of points a.k.a. redundant points
  36. - Polygons with points slightly perturbed from exact rectangles should still convert to rectangles
  37.  
  38. Instructions
  39. ============
  40.  
  41. This is not a hackerrank exercise with a binary pass/fail result on the given tests. Take your time
  42. to analyse the problem. Once you decide on an approach to solve the problem, make sure you are confident
  43. with your understanding of its robustness and limitations. Think of edge cases that can stress test these
  44. limitations and do not hesitate to write actual tests for them. Finally, please keep in mind that we
  45. strongly value well-documented and readable code. For example, it may be helpful to write a paragraph in
  46. the source file documenting your approach, the expected behaviour of your implementation and the key
  47. assumptions you made regarding the input.
  48.  
  49. Submission
  50. ==========
  51.  
  52. Preserve the API of the `fit_into_rectangle` function (so we can easily run it through a test suite)
  53.  
  54. Submit your solution as a single .cpp file and provide the compilation command used to build it.
  55. If you have been asked to submit your solution using codeinterview.io, you can make sure that your
  56. solution builds and passes the tests by pressing the "Run" button at the bottom of the page
  57.  
  58. */
  59.  
  60. constexpr float k_default_epsilon = .0001f;
  61.  
  62. bool almost_equal(float a, float b, float epsilon = k_default_epsilon) {
  63.     return std::abs(a - b) <= epsilon;
  64. }
  65.  
  66. struct Point {
  67.     float x;
  68.     float y;
  69.     friend bool operator==(const Point& lhs, const Point& rhs) {
  70.         return lhs.x == rhs.x and lhs.y == rhs.y;
  71.     }
  72.     friend std::ostream& operator<<(std::ostream& out, const Point& point) {
  73.         return out << "(" << point.x << "," << point.y << ")";
  74.     }
  75. };
  76.  
  77. bool almost_equal(const Point& a, const Point& b, float epsilon = k_default_epsilon) {
  78.     return almost_equal(a.x, b.x, epsilon) and almost_equal(a.y, b.y, epsilon);
  79. }
  80.  
  81. using Polygon = std::vector<Point>;
  82.  
  83. struct Rectangle {
  84.     Point bottom_left;
  85.     Point top_right;
  86.     friend std::ostream& operator<<(std::ostream& out, const Rectangle& rectangle) {
  87.         return out << "Rectangle(" << rectangle.bottom_left << "," << rectangle.top_right << ")";
  88.     }
  89. };
  90.  
  91. bool almost_equal(const Rectangle& a, const Rectangle& b, float epsilon = k_default_epsilon) {
  92.     return almost_equal(a.bottom_left, b.bottom_left, epsilon) and almost_equal(a.top_right, b.top_right, epsilon);
  93. }
  94.  
  95. // ------------------------------------------------------------------------------------------------
  96.  
  97. const float MAX_VALUE = 0xFFFF;
  98. const float MIN_VALUE = -0xFFFF;
  99.  
  100. /**
  101. * Immutable struct that contains two points to represent a vector from p1 -> p2
  102. */
  103. struct Vector {
  104.     const float x;
  105.     const float y;
  106.     const Point origin;
  107.  
  108.     Vector(float x, float y) : x(x), y(y), origin({0, 0}) {}
  109.     Vector(const Point& p1, const Point& p2) : x(p2.x - p1.x), y(p2.y - p1.y), origin(p1) {}
  110.  
  111.     float lengthSquared() const {
  112.         return x * x + y * y;
  113.     }
  114.  
  115.     float length() const {
  116.         return sqrtf(lengthSquared());
  117.     }
  118.  
  119.     // Returns the unit vector - vector with the same direction but length of 1
  120.     Vector normalised() const {
  121.         float len = length();
  122.         return { x / len, y / len };
  123.     }
  124.  
  125.     friend std::ostream& operator<<(std::ostream& out, const Vector& vec) {
  126.         return out << "(" << vec.x << "," << vec.y << ")";
  127.     }
  128. };
  129.  
  130. /**
  131. * Calculates the dot product of two vectors
  132. */
  133. float dot(const Vector& v1, const Vector& v2) {
  134.     return v1.x * v2.x + v1.y * v2.y;
  135. }
  136.  
  137. /**
  138. * Returns a list of Vector structs corresponding to the points of the polygon
  139. */
  140. std::vector<Vector> get_vectors(const Polygon& polygon) {
  141.     std::vector<Vector> vectors;
  142.  
  143.     for (std::size_t i = 1; i < polygon.size(); ++i) {
  144.         vectors.push_back(Vector(polygon[i - 1], polygon[i]));
  145.     }
  146.  
  147.     // Potentially expensive and slow copy
  148.     return vectors;
  149. }
  150.  
  151. std::optional<Rectangle> fit_into_rectangle(const Polygon& polygon, float epsilon = k_default_epsilon) {
  152.  
  153.     std::vector<Vector> vectors = get_vectors(polygon);
  154.  
  155.     bool is_rectangle = false;
  156.     int num_right_angles = 0;
  157.     Point bottom_left = { MAX_VALUE, MAX_VALUE };
  158.     Point top_right = { MIN_VALUE, MIN_VALUE };
  159.  
  160.     // Cannot be rectangle if it's less than 4 points
  161.     if (polygon.size() < 4) return std::nullopt;
  162.     // Check that polygon ends in the same position as the start
  163.     if (!almost_equal(*polygon.begin(), *(polygon.end() - 1)))
  164.         return std::nullopt;
  165.  
  166.     // Loop through pairs of vectors and calculate dot product
  167.     // Stop if number of right angles exceeds 4 because we know it cannot be a rectangle then
  168.     for (std::size_t i = 0; i < vectors.size() && num_right_angles <= 4; ++i) {
  169.  
  170.         // Get the vector pairs - use last vector for v1 for first pair
  171.         Vector v1 = i == 0 ? vectors[vectors.size() - 1] : vectors[i - 1];
  172.         Vector v2 = vectors[i];
  173.  
  174.         // Calculate dot product
  175.         float dot_product = dot(v1.normalised(), v2.normalised());
  176.  
  177.         bool is_right_angle = almost_equal(dot_product, 0);
  178.         bool is_redundant = almost_equal(dot_product, 1);
  179.  
  180.         // If it's not a right angle and the vector pair isn't a straight line (redundant point) then early exit
  181.         if (!is_right_angle && !is_redundant)
  182.             break;
  183.  
  184.         if (is_right_angle) {
  185.             // This vector pair makes a right-angle so find min/max points on each axis to determine bottom_left and top_right
  186.             // The point in interest is between the end point of v1 or start point of v2
  187.             if (v2.origin.x < bottom_left.x)
  188.                 bottom_left.x = v2.origin.x;
  189.             if (v2.origin.y < bottom_left.y)
  190.                 bottom_left.y = v2.origin.y;
  191.  
  192.             if (v2.origin.x > top_right.x)
  193.                 top_right.x = v2.origin.x;
  194.             if (v2.origin.y > top_right.y)
  195.                 top_right.y = v2.origin.y;
  196.  
  197.             num_right_angles++;
  198.         }
  199.     }
  200.  
  201.     // Rectangles must have 4 right angles
  202.     is_rectangle = num_right_angles == 4;
  203.  
  204.     // The question asks only for a Rectangle object if it is axis-aligned
  205.     if (is_rectangle)
  206.     {
  207.         Rectangle rect{bottom_left, top_right};
  208.         return std::make_optional<Rectangle>(rect);
  209.     }
  210.     else
  211.         return std::nullopt;
  212. }
  213.  
  214. // ----------------- UNIT TESTS -----------------
  215. void test_vector() {
  216.     {
  217.         // Classic a^2 + b^2 = c^2 case with sides 3 4 5
  218.         Point p1 = { 2,3 };
  219.         Point p2 = { 5,7 };
  220.         Vector v(p1, p2);
  221.  
  222.         assert(v.length() == 5);
  223.         assert(v.x == 3);
  224.         assert(v.y == 4);
  225.     }
  226.     {
  227.         // Points with negative values
  228.         Point p1 = { -30,-5 };
  229.         Point p2 = { -10,-26 };
  230.         Vector v(p1, p2);
  231.  
  232.         assert(v.length() == 29);
  233.         assert(v.x == 20);
  234.         assert(v.y == -21);
  235.     }
  236.     {
  237.         // Points with floating point imprecision
  238.         Point p1 = { 0, 0 };
  239.         Point p2 = { 1, 2 };
  240.         Vector v(p1, p2);
  241.  
  242.         assert(almost_equal(v.length(), 2.236067f));
  243.     }
  244. }
  245.  
  246. void test_dot_product() {
  247.     {
  248.         // Vectors in the same direction and same magnitude - expect dot product of 1
  249.         Vector v1 = Vector({ 0, 0 }, { 1, 2 });
  250.         Vector v2 = Vector({ 0, 0 }, { 1, 2 });
  251.         assert(almost_equal(dot(v1.normalised(), v2.normalised()), 1));
  252.     }
  253.     {
  254.         // Vectors in the same direction and same magnitude - expect dot product of 1
  255.         Vector v1 = Vector({ 2, 1 }, { 4, 4 });
  256.         Vector v2 = Vector({ 6, 8 }, { 8, 11 });
  257.         assert(almost_equal(dot(v1.normalised(), v2.normalised()), 1));
  258.     }
  259.     {
  260.         // Vectors in the same direction with different magnitudes - dot product of unit vectors, expect value of 1
  261.         Vector v1 = Vector({ 0, 0 }, { 4, 4 });
  262.         Vector v2 = Vector({ 0, 0 }, { 8, 8 });
  263.         assert(almost_equal(dot(v1.normalised(), v2.normalised()), 1));
  264.     }
  265.     {
  266.         // Axis-aligned vectors at right angles
  267.         Vector v1 = Vector({ 0, 0 }, { 5, 0 });
  268.         Vector v2 = Vector({ 0, 0 }, { 0, 5 });
  269.         assert(almost_equal(dot(v1, v2), 0));
  270.     }
  271.     {
  272.         // Not axis-aligned vectors at right angles
  273.         Vector v1 = Vector({ 0, 0 }, { 5, 5 });
  274.         Vector v2 = Vector({ 0, 0 }, { 5, -5 });
  275.         assert(almost_equal(dot(v1, v2), 0));
  276.     }
  277.     {
  278.         // Dot product of vectors with random values
  279.         Vector v1 = Vector({ 5, 1 }, { 9, 8 });
  280.         Vector v2 = Vector({ 4, 3 }, { 2, 10 });
  281.         assert(almost_equal(dot(v1, v2), 41));
  282.         assert(almost_equal(dot(v1.normalised(), v2.normalised()), 0.69853));
  283.     }
  284. }
  285.  
  286. void run_unit_tests() {
  287.     test_vector();
  288.     test_dot_product();
  289. }
  290.  
  291. int main() {
  292.  
  293.     run_unit_tests();
  294.  
  295.     {
  296.         // Case 1: a well formed rectangle
  297.         //   *------*
  298.         //   |      |
  299.         //   |      |
  300.         //   |      |
  301.         //   *------*
  302.         const Polygon polygon({ {6,1},{6,4},{1,4},{1,1},{6,1} });
  303.         const auto rectangle = fit_into_rectangle(polygon);
  304.         assert(rectangle);
  305.         assert(almost_equal(*rectangle, Rectangle{ {1,1},{6,4} }));
  306.     }
  307.  
  308.     {
  309.         // Case 2: a polygon with redundant points
  310.         //   *--*---*
  311.         //   |      |
  312.         //   |      *
  313.         //   |      |
  314.         //   *------*
  315.         const Polygon polygon({ {6,3},{6,5},{3,5},{1,5},{1,1},{6,1},{6,3} });
  316.         const auto rectangle = fit_into_rectangle(polygon);
  317.         assert(rectangle);
  318.         assert(almost_equal(*rectangle, Rectangle{ {1,1},{6,5} }));
  319.     }
  320.  
  321.     {
  322.         // Case 3: a polygon with a diagonal edge
  323.         //   *------*
  324.         //   |       `
  325.         //   |        `
  326.         //   |         `
  327.         //   *----------*
  328.         const Polygon polygon({ {1,1},{10,1},{6,5},{1,5},{1,1} });
  329.         const auto rectangle = fit_into_rectangle(polygon);
  330.         assert(not rectangle);
  331.     }
  332.  
  333.     {
  334.         // Case 4: a polygon with axis-aligned edges, but non-rectangular shape
  335.         //   *------*
  336.         //   |      |
  337.         //   |      *---*
  338.         //   |          |
  339.         //   *----------*
  340.         const Polygon polygon({ {1,1},{10,1},{10,3},{6,3},{6,5},{1,5},{1,1} });
  341.         const auto rectangle = fit_into_rectangle(polygon);
  342.         assert(not rectangle);
  343.     }
  344.  
  345.     {
  346.         // Case 5: a non-axis-aligned rectangle
  347.         //      /\
  348.         //     /  \
  349.         //    /   /
  350.         //    \  /
  351.         //     \/
  352.         const Polygon polygon({ {4,1},{8,5},{5,8},{1,4},{4,1} });
  353.         const auto rectangle = fit_into_rectangle(polygon);
  354.         assert(rectangle);
  355.     }
  356.  
  357.     {
  358.         // Case 6: a polygon with slightly perturbed points, this is bread-and-butter in computational geometry
  359.         //   *------*
  360.         //   |      |
  361.         //   |      |
  362.         //   |      |
  363.         //   *------~
  364.         const Polygon polygon({ {1,1},{6,1.00001},{6,4},{1,4},{1,1} });
  365.         const auto rectangle = fit_into_rectangle(polygon);
  366.         assert(rectangle);
  367.         assert(almost_equal(*rectangle, Rectangle{ {1,1},{6,4} }));
  368.     }
  369.  
  370.     // ---------- My tests ----------
  371.  
  372.     {
  373.         // Case 7: a polygon that does not make a complete shape but all right-angles
  374.         //   *------*
  375.         //          |
  376.         //          |
  377.         //          |
  378.         //          *------~
  379.         const Polygon polygon({ {1,6},{6,6},{6,1},{12,1} });
  380.         const auto rectangle = fit_into_rectangle(polygon);
  381.         assert(not rectangle);
  382.     }
  383.  
  384.     {
  385.         // Case 8: a polygon that has all right-angles but is not a rectangle
  386.         //   *------*
  387.         //   |      |
  388.         //   |   *--*
  389.         //   |   |  |
  390.         //   |   |  |
  391.         //   |   *--*
  392.         //   |      |
  393.         //   *------*
  394.         const Polygon polygon({ {1,6},{1,1},{6,1},{6,2},{5,2},{5,4},{6,4},{6,6},{1,6} });
  395.         const auto rectangle = fit_into_rectangle(polygon);
  396.         assert(not rectangle);
  397.     }
  398.  
  399.     {
  400.         // Case 9: a polygon with all slightly perturbed points
  401.         //   ~------~
  402.         //   |      |
  403.         //   |      |
  404.         //   |      |
  405.         //   ~------~
  406.         const Polygon polygon({ {0.99999,1.00001},{5.99999,1.00001},{6.00001,3.99999},{1.00001,3.99999},{1.00001,0.99999} });
  407.         const auto rectangle = fit_into_rectangle(polygon);
  408.         assert(rectangle);
  409.         assert(almost_equal(*rectangle, Rectangle{ {1,1},{6,4} }));
  410.     }
  411.  
  412.     {
  413.         // Case 10: a polygon with many redundant points
  414.         //   *--**--*
  415.         //   |      |
  416.         //   *      |
  417.         //   |      |
  418.         //   *-****-*
  419.         const Polygon polygon({ {1,6}, {1,3}, {1,1}, {2,1}, {3,1}, {4,1}, {6,1}, {6,6}, {4,6}, {3,6}, {1,6} });
  420.         const auto rectangle = fit_into_rectangle(polygon);
  421.         assert(rectangle);
  422.         assert(almost_equal(*rectangle, Rectangle{ {1,1},{6,6} }));
  423.     }
  424.  
  425.     {
  426.         // Case 11: Empty polygon
  427.         const Polygon polygon({ });
  428.         const auto rectangle = fit_into_rectangle(polygon);
  429.         assert(not rectangle);
  430.     }
  431.  
  432.     {
  433.         // Case 11: Triangle
  434.         const Polygon polygon({ {0,0}, {1,1}, {0, 1}, {0,0} });
  435.         const auto rectangle = fit_into_rectangle(polygon);
  436.         assert(not rectangle);
  437.     }
  438.  
  439.     {
  440.         // Case 11: Incomplete polygon
  441.         const Polygon polygon({ {0,0}, {1,1}, {0, 1} });
  442.         const auto rectangle = fit_into_rectangle(polygon);
  443.         assert(not rectangle);
  444.     }
  445.  
  446.     return 0;
  447. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement