Sunday 18 September 2016

Data Structure and Algorithm - Find Path between Sensors

1. Problem Description
This is a Google interview question for software develop engineer from careercup. Here is the original thread,
"
Given a rectangle with top-left(a,b) and bottom-right(c,d) coordinates. Also given some coordinates (m,n) of sensors inside the rectangle. All sensors can sense in a circular region of radius r about their centre (m,n). Total N sensors are given. A player has to reach from left side of rectangle to its right side (i.e. he can start his journey from any point whose y coordinate is b and x coordinate is a<=x<=c. He has to end his journey to any point whose y coordinate is d and x coordinate is a<=x<=c).

Write an algorithm to find path (possibly shortest but not necessary) from start to end as described above.

Note: all coordinates are real numbers

(a,b)
|----------------------------------------------|
|.............................................................|end
|.............................................................|
|start......................................................|
|.............................................................|
|----------------------------------------------|(c,d)

Edit: You have to avoid sensors.
Also u can move in any direction any time.
- ankit3600 June 14, 2016 in India | Report Duplicate | Flag 
".

2. Data Structure and Algorithm
Determine if path exists:
Figure 1 and Figure 2 are talking about vertical path. Figure 1 shows a case of having a vertical path and Figure 2 shows a case of not having a vertical path.

Figure 1: Have a Vertical Path

Figure 2: Does Not Have a Vertical Path
The sensors are merged into sensor ranges if they are crossed with each other. Given a rectangle and sensors in it, there is no vertical path if there exists any merged sensor range that crosses both left and right bounds of the rectangle. And there is no horizontal path if there exists any merged sensor range that crosses both top and bottom bounds of the rectangle.
In Figure 2 sensors, S1, S2, S3, S4, S5, S6 and S7, are merged together and cross both left and right bounds of the rectangle, therefore there is not vertical path. However in Figure 1 there does not exists such sensor range, therefore there are paths cross top and bottom boundary.

Merge sensors as sensor range if they are crossed:
Two sensors are crossed if the distance between their center is not greater than sum of their radius. If two sensors are crossed, they will be merged as a sensor range.
Data structure of Sensor range:
    - A list of sensors which they are crossed
    - Four crosses - each with one boundary of the rectangle (left, right, top and bottom)
        * The crosses are tracked and updated when a new sensor joins
        * If both left and right crosses exist, then there is no horizontal path
        * If both top and bottom crosses, exist, then there is no vertical path

How a new sensor is added:
One sensor and one sensor range are crossed if this sensor is crossed with any sensor consisting of this sensor range.
Check if this new sensor is crossed with any existing sensor ranges.
    - Add a new sensor range (consisting of this single sensor), if not crossed with
       any other sensor range
    - Merge this sensor with all crossed sensor ranges to form a new sensor range
      The new sensor range includes:
          * A list of sensors: this sensor and all sensors consisting of crossed sensor ranges
          * Merge the crosses with rectangle of this sensor and of crossed sensor ranges

Figure 3: Merge Sensors
For example in Figure 3,
    - SR1 (sensor range 1) has one sensor S1
    - SR3 (sensor range 3) has one sensor S3
Now add S2, then S2 crosses with two sensor ranges SR1 and SR3. Then S1, S2 and S3 forms a new sensor range.
And merge the cross of SR1 and SR2 and S2 together,
    - SR1 has top cross [P1, P2]
    - SR3 has top cross [P3, P4]
    - S2 does not cross with rectangle
Then the merged cross is [P1, P4]
    - Lower bound = min(all low bounds)
    - Upper bound = max(all upper bounds)

Corner Case of Merge Crosses:
Figure 4 shows the corner case when detecting vertical path. In this example in Figure 4, SR1 consists of 4 sensors - S1, S2, S3 and S4. SR1 has a merged top cross with rectangle as [P1, P2]. The corner case here is that SR1 also has a valid left cross with rectangle.

Figure 4: Merge Crosses - Corner Case
Therefore any point in the range of [Ps, P1) is not a valid starting point of a vertical path. So SR1 should have a top cross with rectangle of [Ps, P2]. The same as the sensor range at the right bottom corner with a bottom cross of [P1, Pe].

The Path returned:
After senors are merged into sensor ranges, the complement ranges of sensor ranges 4 crosses on rectangle boundaries are populated. For example in Figure 5, P1 and P2 are in complement ranges of top boundary, and P3 and P4 are in the complement ranges of bottom boundary. And combinations of point from complement ranges of top and bottom can form a valid vertical path.

Figure 5: Cross Path
In this implementation it is to find the first complement range of pair (top cross, bottom cross) in vertical path search or (left cross, right cross) in horizontal path search. And then return the mid point of the range.

I have implemented the solution in two languages. The C++ implementation is provided in the next section and the Python implementation is on my Python post.

3. C++ Implementation
// ********************************************************************************
// Implementation
// ********************************************************************************
#include <array>
#include <limits>
#include <set>
#include <vector>
#include <cassert>
#include <cmath>
struct Location
{
    Location()
        : x(-1.0)
        , y(-1.0)
    {}
    Location(double x_, double y_)
        : x(x_)
        , y(y_)
    {}
    double x;
    double y;
    static double GetDistance(const Location &l1, const Location &l2) {
        const double deltaX = l1.x - l2.x;
        const double deltaY = l1.y - l2.y;
        return sqrt(deltaX*deltaX + deltaY*deltaY);
    }
    bool operator==(const Location &rhs) const {
        return x == rhs.x && y == rhs.y;
    }
};
struct EdgeCross
{
    EdgeCross()
    {}
    EdgeCross(double lb, double ub)
        : lowerBound(lb)
        , upperBound(ub)
    {}
    inline void SetBounds(double lb, double ub)
    {
        lowerBound = lb;
        upperBound = ub;
    }
    inline void SetLowerBound(double lb)
    {
        lowerBound = lb;
    }
    inline void SetUpperBound(double ub)
    {
        upperBound = ub;
    }
    bool IsValid() const {
        return lowerBound != std::numeric_limits<double>::max() &&
               upperBound != std::numeric_limits<double>::min();
    }
    double lowerBound = std::numeric_limits<double>::max();
    double upperBound = std::numeric_limits<double>::min();
    void MergeBounds(const EdgeCross &rhs)
    {
        SetBounds(std::min(lowerBound, rhs.lowerBound),
                  std::max(upperBound, rhs.upperBound));
    }
    enum EDGE
    {
        LEFT = 0UL,
        TOP,
        RIGHT,
        BOTTOM,
        MAX
    };
    using EdgeCrosses = std::array<EdgeCross, EDGE::MAX>;
};
struct EdgeCrossCmpLess
{
    bool operator() (const EdgeCross *lhs, const EdgeCross *rhs) const {
        return lhs->upperBound < rhs->lowerBound;
    }
};
struct Sensor
{
    Sensor()
    {}
    Sensor(const Location &cnter, double rds)
        : center(cnter)
        , radius(rds)
    {
        assert(radius >= 0.0);
    }
    Location center;
    double radius = 0.0;
    bool Crossed(const Sensor &rhs) const {
        double dist = Location::GetDistance(this->center, rhs.center);
        return dist <= fabs(this->radius + rhs.radius);
    }
    bool CrossedWithHLine(const double Y, EdgeCross& ec) const {
        if (Y >= (center.y - radius) && Y <= (center.y + radius)) {
            const double deltaY = center.y - Y;
            const double deltaX = sqrt(radius*radius - deltaY*deltaY);
            ec.SetBounds(center.x - deltaX, center.x + deltaX);
            return true;
        }
        return false;
    }
    bool CrossedWithVLine(const double X, EdgeCross& ec) const {
        if (X >= (center.x - radius) && X <= (center.x + radius)) {
            const double deltaX = center.x - X;
            const double deltaY = sqrt(radius*radius - deltaX*deltaX);
            ec.SetBounds(center.y - deltaY, center.y + deltaY);
            return true;
        }
        return false;
    }
};
struct SensorRange
{
    SensorRange()
    {}
    EdgeCross::EdgeCrosses edgeCrosses;
    std::vector<const Sensor *> sensors;
    void MergeEdgeCross(const EdgeCross::EdgeCrosses &ecs) {
        for (size_t idx = 0; idx < ecs.size(); ++idx) {
            edgeCrosses[idx].MergeBounds(ecs[idx]);
        }
    }
    void Merge(const SensorRange &sr) {
        MergeEdgeCross(sr.edgeCrosses);
        sensors.insert(sensors.end(), sr.sensors.begin(), sr.sensors.end());
    }
};
struct Rectangle {
    Rectangle()
    {}
    Rectangle(const double l, const double r, const double t, const double b)
    {
        assert(l < r);
        assert(b < t);
        edges[EdgeCross::LEFT] = l;
        edges[EdgeCross::RIGHT] = r;
        edges[EdgeCross::TOP] = t;
        edges[EdgeCross::BOTTOM] = b;
    }
    std::array<double, EdgeCross::MAX> edges;
};
void FindCrossBetweenSensorAndRect(const Sensor &sensor,
                                   const Rectangle &rect,
                                   EdgeCross::EdgeCrosses &edgeCrosses)
{
    for (size_t edge = EdgeCross::LEFT; edge < EdgeCross::MAX; ++edge) {
        if (edge & 1UL) {
            sensor.CrossedWithHLine(rect.edges[edge], edgeCrosses[edge]);
        }
        else {
            sensor.CrossedWithVLine(rect.edges[edge], edgeCrosses[edge]);
        }
    }
}

// Merge sensors into sensor ranges in Figure 3
bool GenerateSenorRangeMap(const std::vector<Sensor> &sensors,
                           const Rectangle &rect,
                           const bool breakIfNoPath,
                           const bool vPath,
                           std::vector<SensorRange> &sensorRanges)
{
    auto itEnd = sensors.cend();
    std::vector<size_t> toMerge;
    for (auto it = sensors.cbegin(); it != itEnd; ++it) {
        const size_t CUR_SIZE = sensorRanges.size();
        toMerge.clear();
        toMerge.reserve(CUR_SIZE);
        // populate all sensor ranges crossed with this sensor
        for (size_t idx = 0; idx < CUR_SIZE; ++idx) {
            auto&& sRange = sensorRanges[idx];
            auto itSenEnd = sRange.sensors.cend();
            for (auto itSen = sRange.sensors.cbegin(); itSen != itSenEnd; ++itSen) {
                if ((*itSen)->Crossed(*it)) {
                    toMerge.push_back(idx);
                    break;
                }
            }
        }
        const SensorRange *workingSR = NULL;
        if (toMerge.empty()) {
            // create a new sensor range if not crossed with any sensor ranges
            SensorRange sr;
            sr.sensors.emplace_back(&(*it));
            FindCrossBetweenSensorAndRect(*it, rect, sr.edgeCrosses);
            sensorRanges.push_back(sr);
            workingSR = &sensorRanges.back();
        }
        else {
            // merge all existing sensor range
            // 1. merge the rest into the first senror range
            // 2. erase the sensor ranges except the very first
            auto itMergeREnd = toMerge.crend() - 1;
            for (auto itMergeR = toMerge.crbegin(); itMergeR != itMergeREnd; ++itMergeR) {
                auto&& sRangeToKeep = sensorRanges[*toMerge.begin()];
                auto&& sRangeToMerge = sensorRanges[*itMergeR];
                sRangeToKeep.Merge(sRangeToMerge);
                sensorRanges.erase(sensorRanges.begin() + *itMergeR);
            }
            // merge with the sensor
            EdgeCross::EdgeCrosses edgeCrosses;
            FindCrossBetweenSensorAndRect(*it, rect, edgeCrosses);
            auto&& sRangeToKeep = sensorRanges[*toMerge.begin()];
            sRangeToKeep.MergeEdgeCross(edgeCrosses);
            sRangeToKeep.sensors.push_back(&(*it));
            workingSR = &sRangeToKeep;
        }
        if (workingSR && breakIfNoPath) {
            if ((vPath &&
                 workingSR->edgeCrosses[EdgeCross::LEFT].IsValid() &&
                 workingSR->edgeCrosses[EdgeCross::RIGHT].IsValid()) ||
                (!vPath &&
                 workingSR->edgeCrosses[EdgeCross::TOP].IsValid() &&
                 workingSR->edgeCrosses[EdgeCross::BOTTOM].IsValid())) {
                    return false;
            }
        }
    }
    return true;
}

// Process the corner case in Figure 4
bool ProcessCornerCase(const bool vPath,
                       const EdgeCross::EdgeCrosses &edgeCrosses,
                       const Rectangle &rect,
                       EdgeCross &edge)
{
    if (edge.IsValid()) {
        if (vPath) {
            // left corner
            if (edgeCrosses[EdgeCross::LEFT].IsValid()) {
                edge.SetLowerBound(rect.edges[EdgeCross::LEFT]);
            }
            // right corner
            if (edgeCrosses[EdgeCross::RIGHT].IsValid()) {
                edge.SetUpperBound(rect.edges[EdgeCross::RIGHT]);
            }
        }
        else {
            // top corner
            if (edgeCrosses[EdgeCross::TOP].IsValid()) {
                edge.SetUpperBound(rect.edges[EdgeCross::TOP]);
            }
            // bottom corner
            if (edgeCrosses[EdgeCross::BOTTOM].IsValid()) {
                edge.SetLowerBound(rect.edges[EdgeCross::BOTTOM]);
            }
        }
        return true;
    }
    return false;
}

using Path = std::array<Location, 2>;
using EdgeCrossSet = std::set<const EdgeCross*, EdgeCrossCmpLess>;
// Get the mid point in Figure 5
bool GetMidPointOfFirstGap(const EdgeCrossSet &ecs,
                           const Rectangle &rect,
                           const bool vPath,
                           double &val)
{
    const double lowerBound = vPath ?
        rect.edges[EdgeCross::LEFT] : rect.edges[EdgeCross::BOTTOM];
    const double upperBound = vPath ?
        rect.edges[EdgeCross::RIGHT] : rect.edges[EdgeCross::TOP];
    if (ecs.empty()) {
        val = (lowerBound + upperBound) / 2;
        return true;
    }
    auto itFirst = ecs.cbegin();
    if ((*itFirst)->lowerBound > lowerBound) {
        val = (lowerBound + (*itFirst)->lowerBound) / 2;
        return true;
    }
    auto itSecond = ++ecs.cbegin();
    if (itSecond == ecs.cend()) {
        if ((*itFirst)->upperBound < upperBound) {
            val = ((*itFirst)->upperBound + upperBound) / 2;
            return true;
        }
        return false;
    }
    if ((*itFirst)->upperBound < (*itSecond)->lowerBound) {
        val = ((*itFirst)->upperBound + (*itSecond)->lowerBound) / 2;
        return true;
    }
    return false;
}

bool FindPath(const std::vector<Sensor> &sensors,
              const Rectangle &rect,
              const bool vPath,
              Path &path)
{
    std::vector<SensorRange> sensorRanges;
    if (!sensors.empty()) {
        // Merge sensors into sensor ranges
        if (!GenerateSenorRangeMap(sensors, rect, true, vPath, sensorRanges)) {
            return false;
        }
    }

    // Check if path exist
    if (sensorRanges.empty()) {
        if (vPath) {
            path[0] = Location{ rect.edges[EdgeCross::LEFT],
                                             rect.edges[EdgeCross::TOP] };
            path[1] = Location{ rect.edges[EdgeCross::LEFT],
                                             rect.edges[EdgeCross::BOTTOM] };
        }
        else {
            path[0] = Location{ rect.edges[EdgeCross::LEFT],
                                             rect.edges[EdgeCross::TOP] };
            path[1] = Location{ rect.edges[EdgeCross::RIGHT],
                                             rect.edges[EdgeCross::TOP] };
        }
        return true;
    }
    EdgeCrossSet ecsFrom;
    EdgeCrossSet ecsTo;
    auto itEnd = sensorRanges.end();
    for (auto it = sensorRanges.begin(); it != itEnd; ++it) {
        // Process the corner case shown in Figure 4
        // And populate the first complement range
        if (vPath && ProcessCornerCase(vPath,
                                       it->edgeCrosses,
                                       rect,
                                       it->edgeCrosses[EdgeCross::TOP])) {
            ecsFrom.insert(&(it->edgeCrosses[EdgeCross::TOP]));
        }
        if (vPath && ProcessCornerCase(vPath,
                                       it->edgeCrosses,
                                       rect,
                                       it->edgeCrosses[EdgeCross::BOTTOM])) {
            ecsTo.insert(&(it->edgeCrosses[EdgeCross::BOTTOM]));
        }
        if (!vPath && ProcessCornerCase(vPath,
                                        it->edgeCrosses,
                                        rect,
                                        it->edgeCrosses[EdgeCross::LEFT])) {
            ecsFrom.insert(&(it->edgeCrosses[EdgeCross::LEFT]));
        }
        if (!vPath && ProcessCornerCase(vPath,
                                        it->edgeCrosses,
                                        rect,
                                        it->edgeCrosses[EdgeCross::RIGHT])) {
            ecsTo.insert(&(it->edgeCrosses[EdgeCross::RIGHT]));
        }
    }
    double valFrom, valTo;
    // Return the path (the mid point of the first complement range pair)
    if (GetMidPointOfFirstGap(ecsFrom, rect, vPath, valFrom) &&
        GetMidPointOfFirstGap(ecsTo, rect, vPath, valTo)) {
        path[0] = vPath ? Location(valFrom, rect.edges[EdgeCross::TOP]) :
                          Location(rect.edges[EdgeCross::LEFT], valFrom);
        path[1] = vPath ? Location(valTo, rect.edges[EdgeCross::BOTTOM]) :
                          Location(rect.edges[EdgeCross::RIGHT], valTo);
        return true;
    }
    return false;
}
// ********************************************************************************
// Test
// ********************************************************************************
void TestFindPathBetweenSensors()
{
    Path path;
    const Rectangle rect = { 1.0, 9.0, 8.0, 1.0 };
    {
        const std::vector<Sensor> sensors{ { { 2.0, 3.0 }, 1.0 },
                                           { { 4.0, 3.0 }, 1.0 },
                                           { { 6.0, 3.0 }, 1.0 },
                                           { { 8.0, 3.0 }, 1.0 }
                                         };
        assert(FindPath(sensors, rect, true, path) == false);
        assert(FindPath(sensors, rect, false, path) == true);
        assert(path.at(0) == Location(1.0, 2.0) && path.at(1) == Location(9.0, 2.0));
    }
    {
        const std::vector<Sensor> sensors{ { { 2.0, 3.0 }, 1.0 },
                                           { { 4.0, 3.0 }, 1.0 },
                                           { { 6.0, 3.0 }, 1.0 },
                                           { { 8.0, 3.0 }, 1.0 },
                                           { { 8.0, 8.0 }, 1.0 },
                                           { { 7.0, 7.0 }, 1.0 }
                                         };
        assert(FindPath(sensors, rect, true, path) == false);
        assert(FindPath(sensors, rect, false, path) == true);
        assert(path.at(0) == Location(1.0, 2.0) && path.at(1) == Location(9.0, 2.0));
    }
    {
        const std::vector<Sensor> sensors{ { { 2.0, 7.2 }, 1.0 },
                                           { { 3.0, 6.0 }, 1.0 },
                                           { { 4.0, 4.5 }, 1.0 },
                                           { { 8.0, 7.2 }, 1.0 },
                                           { { 7.0, 6.2 }, 1.0 },
                                           { { 2.0, 3.0 }, 1.0 },
                                           { { 3.0, 4.0 }, 1.0 },
                                           { { 9.0, 1.0 }, 1.0 }
                                         };
        assert(FindPath(sensors, rect, true, path) == true);
        assert(path.at(0) == Location(5.0, 8.0) && path.at(1) == Location(4.5, 1.0));
        assert(FindPath(sensors, rect, false, path) == true);
        assert(path.at(0) == Location(1.0, 2.0) && path.at(1) == Location(9.0, 4.6));
    }
}

No comments:

Post a Comment