I am working through the book Algorithms in a Nutshell by George T. Heineman and trying to translate the pseudocodes into Rust.
Here is a naive convex hull algorithm:
use std::cmp::Ordering::Equal;
use std::cmp::Ordering;
use std::ops::{Add, Sub, Mul};
use std::collections::BTreeSet;
#[derive(Debug, Copy, Clone)]
pub struct Point {
x: f64,
y: f64,
}
impl PartialEq for Point {
fn eq(&self, other: &Point) -> bool {
if self.x.is_nan() & other.x.is_nan() & self.y.is_nan() & other.y.is_nan() {
return true;
}
if self.x.is_nan() & other.x.is_nan() {
return self.y == other.y;
}
if self.y.is_nan() & other.y.is_nan() {
return self.x == other.x;
}
(self.x == other.x) & (self.y == other.y)
}
}
impl Eq for Point {}
impl PartialOrd for Point {
fn partial_cmp(&self, other: &Point) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Point {
fn cmp(&self, other: &Point) -> Ordering {
let c = self.x.partial_cmp(&other.x).unwrap();
if c == Equal {
self.y.partial_cmp(&other.y).unwrap()
} else {
c
}
}
}
impl Point {
pub fn new(x: f64, y: f64) -> Point {
if x.is_nan() | y.is_nan() {
panic!("Coordinates cannot be NaN!");
}
Point {
x: x,
y: y,
}
}
// Euclidean distance
fn distance(&self, other: &Point) -> f64 {
((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt()
}
// Draw a horizontal line through this point, connect this point with the other, and measure the angle between these two lines.
fn angle(&self, other: &Point) -> f64 {
(other.y - self.y).atan2(other.x - self.x)
}
}
impl Add for Point {
type Output = Point;
fn add(self, rhs: Point) -> Point {
Point::new(self.x + rhs.x, self.y + rhs.y)
}
}
impl Sub for Point {
type Output = Point;
fn sub(self, rhs: Point) -> Point {
Point::new(self.x - rhs.x, self.y - rhs.y)
}
}
// dot product
impl Mul for Point {
type Output = f64;
fn mul(self, rhs: Point) -> f64 {
self.x * rhs.x + self.y * rhs.y
}
}
#[test]
fn test_point() {
use std::f64::consts::PI;
let p1 = Point::new(0.0, 0.0);
let p2 = Point::new(0.0, 1.0);
assert_eq!(p1.angle(&p2), PI/2.0);
assert_eq!(p1.distance(&p2), 1.0);
let p1 = Point::new(0.0, 0.0);
let p2 = Point::new(1.0, 1.0);
assert_eq!(p1.angle(&p2), PI/4.0);
assert_eq!(p1.distance(&p2), 2.0f64.sqrt());
let p1 = Point::new(0.0, 0.0);
let p2 = Point::new(1.0, -1.0);
assert_eq!(p1.angle(&p2), -PI/4.0);
assert_eq!(p1.distance(&p2), 2.0f64.sqrt());
}
#[derive(PartialEq,Eq,Debug)]
struct Triangle {
p0: Point,
p1: Point,
p2: Point,
}
impl Triangle {
fn new(p0: Point, p1: Point, p2: Point) -> Triangle {
// Sort by x-coordinate to make sure the first point is the leftmost and lowest.
let mut v: Vec<Point> = vec![p0, p1, p2];
v.sort();
Triangle {
p0: v[0],
p1: v[1],
p2: v[2],
}
}
fn range_x(&self) -> (f64, f64) {
(self.p0.x, self.p2.x)
}
fn range_y(&self) -> (f64, f64) {
let mut v = vec![self.p0, self.p1, self.p2];
v.sort_by(|a, b| {
a.y.partial_cmp(&(b.y)).unwrap_or(Equal)
});
(v[0].y, v[2].y)
}
// Barycentric Technique, check whether point is in triangle, see http://blackpawn.com/texts/pointinpoly/
fn contains(&self, p: Point) -> bool {
let v0 = self.p2 - self.p0;
let v1 = self.p1 - self.p0;
let v2 = p - self.p0;
let dot00 = v0 * v0;
let dot01 = v0 * v1;
let dot02 = v0 * v2;
let dot11 = v1 * v1;
let dot12 = v1 * v2;
let inv_denom = 1.0f64 / (dot00 * dot11 - dot01 * dot01);
let u = (dot11 * dot02 - dot01 * dot12) * inv_denom;
let v = (dot00 * dot12 - dot01 * dot02) * inv_denom;
(u > 0.0) & (v > 0.0) & (u + v < 1.0)
}
}
#[macro_export]
macro_rules! btreeset {
($($x: expr),*) => {{
let mut set = ::std::collections::BTreeSet::new();
$( set.insert($x); )*
set
}}
}
pub fn convex_hull_naive(points: &BTreeSet<Point>) -> BTreeSet<Point> {
// you must have at least 3 points, otherwise there is no hull
assert!(points.len() > 2);
// a fn for removing just one point from the set
let minus_one = |p: &Point| {
points.difference(&(btreeset!(p.clone()))).cloned().collect::<BTreeSet<Point>>()
};
// set of points that are marked as internal
let mut p_internal_set: BTreeSet<Point> = BTreeSet::new();
// check permutations of 4 points, check if the fourth point is contained in the triangle
for p_i in points {
let minus_i = minus_one(&p_i);
for p_j in &minus_i {
let minus_j = minus_one(&p_j);
for p_k in &minus_j {
let minus_k = minus_one(&p_k);
for p_m in &minus_k {
if Triangle::new(p_i.clone(), p_j.clone(), p_k.clone()).contains(p_m.clone()) {
p_internal_set.insert(p_m.clone());
}
}
}
}
}
// set of points that are not internal
let mut hull = points.difference(&p_internal_set).cloned().collect::<Vec<Point>>();
// sort by coordinates so that the first point is the leftmost
hull.sort();
let head = hull[0].clone();
let angle_to_head = |p: &Point| {
if p == &head {
0.0
} else {
head.angle(p)
}
};
// sort by the angle with the first point
// when that is equal, sort by distance to head
hull.sort_by(|a, b| {
let angle_a = angle_to_head(&a);
let angle_b = angle_to_head(&b);
angle_a.partial_cmp(&angle_b).unwrap()
});
hull.into_iter().collect::<BTreeSet<Point>>()
// return vec![Point::new(1.0, 1.0)];
}
#[test]
fn test_convex_hull_naive() {
let points = (0..4).into_iter().flat_map(move |i| {
let i = i as f64;
(0..4).into_iter().map(move |j| {
let j = j as f64;
Point::new(i, j)
})
}).collect::<BTreeSet<Point>>();
// let mut points: BTreeSet<Point> = BTreeSet::new();
// for i in 0..4 {
// let i = i as f64;
// for j in 0..4 {
// let j = j as f64;
// points.insert(Point::new(i, j));
// }
// }
assert_eq!((&points).len(), 16);
let hull = convex_hull_naive(&points);
let hull_should_be = btreeset!(
Point::new(0.0, 0.0),
Point::new(1.0, 0.0),
Point::new(2.0, 0.0),
Point::new(3.0, 0.0),
Point::new(3.0, 1.0),
Point::new(3.0, 2.0),
Point::new(3.0, 3.0),
Point::new(2.0, 3.0),
Point::new(1.0, 3.0),
Point::new(0.0, 3.0),
Point::new(0.0, 2.0),
Point::new(0.0, 1.0)
);
assert_eq!(hull, hull_should_be);
}
#[test]
fn test_triangle() {
let p2 = Point::new(0.0, 0.0);
let p1 = Point::new(0.0, 1.0);
let p0 = Point::new(1.0, 1.0);
let t = Triangle::new(p0, p1, p2);
assert_eq!(t.range_x(), (0.0, 1.0));
assert_eq!(t.range_y(), (0.0, 1.0));
assert_eq!(t.p0, p2);
assert_eq!(t.p1, p1);
assert_eq!(t.p2, p0);
// triangle should not contain its vertices
assert!(!t.contains(p0));
assert!(!t.contains(p1));
assert!(!t.contains(p2));
// triangle should contain points on any side
assert!(!t.contains(Point::new(0.5, 0.5)));
assert!(!t.contains(Point::new(0.3, 0.3)));
assert!(!t.contains(Point::new(0.2, 0.2)));
assert!(!t.contains(Point::new(0.1, 0.1)));
assert!(!t.contains(Point::new(0.0, 0.1)));
assert!(!t.contains(Point::new(0.0, 0.2)));
assert!(!t.contains(Point::new(0.1, 1.0)));
assert!(!t.contains(Point::new(0.2, 1.0)));
assert!(!t.contains(Point::new(0.2, 1.1)));
// strictly inside the triangle
assert!(t.contains(Point::new(0.5, 0.51)));
assert!(t.contains(Point::new(0.5, 0.52)));
assert!(t.contains(Point::new(0.5, 0.53)));
let p2 = Point::new(0.0, 0.0);
let p1 = Point::new(0.5, 1.0);
let p0 = Point::new(1.0, 0.5);
let t = Triangle::new(p0, p1, p2);
assert_eq!(t.range_x(), (0.0, 1.0));
assert_eq!(t.range_y(), (0.0, 1.0));
assert_eq!(t.p0, p2);
assert_eq!(t.p1, p1);
assert_eq!(t.p2, p0);
}
All suggestions are welcome.