I would like to calculate the convex hull of a set of points. Most algorithms I've found online return a list of points but I need a list of indices to the points.
To do this I've taken some existing code that calculates the points and tried changing it to instead return the points indices.
Original function to return a vector of convex hull points
#include <iostream>
#include <vector>
#include <tuple>
using namespace std;
typedef std::tuple<int, int> point;
// returns true if the three points make a counter-clockwise turn
bool ccw(const point &a, const point &b, const point &c) {
return ((std::get<0>(b) - std::get<0>(a)) * (std::get<1>(c) - std::get<1>(a)))
> ((std::get<1>(b) - std::get<1>(a)) * (std::get<0>(c) - std::get<0>(a)));
}
std::vector<point> convexHull(std::vector<point> p) {
if (p.size() == 0) return std::vector<point>();
std::sort(p.begin(), p.end(), [](point &a, point &b) {
if (std::get<0>(a) < std::get<0>(b)) return true;
return false;
});
std::vector<point> h;
// lower hull
for (const auto &pt: p) {
while (h.size() >= 2 && !ccw(h.at(h.size() - 2), h.at(h.size() - 1), pt)) {
h.pop_back();
}
h.push_back(pt);
}
// upper hull
auto t = h.size() + 1;
for (auto it = p.crbegin(); it != p.crend(); it = std::next(it)) {
auto pt = *it;
while (h.size() >= t && !ccw(h.at(h.size() - 2), h.at(h.size() - 1), pt)) {
h.pop_back();
}
h.push_back(pt);
}
h.pop_back();
return h;
}
int main() {
using namespace std;
vector<point> points = {
make_pair(16, 3), make_pair(12, 17), make_pair(0, 6), make_pair(-4, -6), make_pair(16, 6),
make_pair(16, -7), make_pair(16, -3), make_pair(17, -4), make_pair(5, 19), make_pair(19, -8),
make_pair(3, 16), make_pair(12, 13), make_pair(3, -4), make_pair(17, 5), make_pair(-3, 15),
make_pair(-3, -9), make_pair(0, 11), make_pair(-9, -3), make_pair(-4, -2), make_pair(12, 10)
};
auto hull = convexHull(points);
for (const auto &point: hull) {
std::cout << get<0>(point) << ", " << get<1>(point) << std::endl;
}
return 0;
}
Result
-9, -3
-3, -9
19, -8
17, 5
12, 17
5, 19
-3, 15
Ive taken this code and also added an indices
vector. I've then tried adding the current index in each loop to this vector to return the indices based on the points passed into the function.
New function changed to return indices of points
#include <iostream>
#include <vector>
#include <tuple>
using namespace std;
typedef std::tuple<int, int> point;
// returns true if the three points make a counter-clockwise turn
bool ccw(const point &a, const point &b, const point &c) {
return ((std::get<0>(b) - std::get<0>(a)) * (std::get<1>(c) - std::get<1>(a)))
> ((std::get<1>(b) - std::get<1>(a)) * (std::get<0>(c) - std::get<0>(a)));
}
std::vector<int> convexHull(std::vector<point> p) {
if (p.size() == 0) return {};
std::sort(p.begin(), p.end(), [](point &a, point &b) {
if (std::get<0>(a) < std::get<0>(b)) return true;
return false;
});
std::vector<point> h;
std::vector<int> indices;
// lower hull
for (int i = 0; i < p.size(); i++) {
auto pt = p[i];
while (h.size() >= 2 && !ccw(h.at(h.size() - 2), h.at(h.size() - 1), pt)) {
h.pop_back();
indices.pop_back();
}
h.push_back(pt);
indices.push_back(i);
}
// upper hull
auto t = h.size() + 1;
for (int i = p.size() - 1; i >= 0; i--) {
auto pt = p[i];
while (h.size() >= t && !ccw(h.at(h.size() - 2), h.at(h.size() - 1), pt)) {
h.pop_back();
indices.pop_back();
}
h.push_back(pt);
indices.push_back(i);
}
h.pop_back();
indices.pop_back();
return indices;
}
int main() {
using namespace std;
vector<point> points = {
make_pair(16, 3), make_pair(12, 17), make_pair(0, 6), make_pair(-4, -6), make_pair(16, 6),
make_pair(16, -7), make_pair(16, -3), make_pair(17, -4), make_pair(5, 19), make_pair(19, -8),
make_pair(3, 16), make_pair(12, 13), make_pair(3, -4), make_pair(17, 5), make_pair(-3, 15),
make_pair(-3, -9), make_pair(0, 11), make_pair(-9, -3), make_pair(-4, -2), make_pair(12, 10)
};
auto hull = convexHull(points);
for (const auto &index: hull) {
std::cout << get<0>(points[index]) << ", " << get<1>(points[index]) << std::endl;
}
return 0;
}
Result
16, 3
-4, -6
12, 10
-9, -3
3, -4
19, -8
16, 6
I expected to see the same results.
What am I missing?
In your program/function you are sorting the points, then you calculate the indices along with the hull. And you return the indices (based on the sorted vector). You then apply the indices to the vector, which is not sorted.
Remember, C++ can pass by reference or value/copy (I am a bit vague maybe but I hope you get the idea; if you have no idea what I am talking about then that is a topic you have to learn about).
What you can do in the second program is: sort the vector before you print it. Maybe that will give you the expected result.
A better solution takes a bit more effort: Pass by reference