0

I'd like to have a container of points that are sorted by the distance to some point, which is given at runtime. In other languages, I could supply the container with a custom compare function, however, I understand this is not possible in rust.

Consider the following code problem:

/// distance between two points
fn distance(a: &(f32, f32), b: &(f32, f32)) -> f32 {
    ((a.0-b.0)*(a.0-b.0) + (a.1-b.1)*(a.1-b.1)).sqrt()
}

fn main() {
  let origin = (1, 1);                 // assume values are provided at runtime
  let mut container = BTreeSet::new(); // should be sorted by distance to origin 
  container.insert((1 ,9));
  container.insert((2 ,2));
  container.insert((1 ,5));
}

After the insertions I want the container to be sorted as [(2,2),(1,5),(1,9)]. The example uses BTreeSet, which I don't insist on using, but it feels like the closest to what I need.

I do NOT want a Vec that I have to resort manually after every insert().

So how do I connect distance(), origin, and container, preferably without third-party dependencies?

Seriously
  • 884
  • 1
  • 11
  • 25
  • What do you mean "connect"? What do you mean by "distance to some"? Is it distance to...origin? Some fixed point? – tadman Dec 29 '22 at 00:45
  • 1
    Does this answer your question? [How do I use a custom comparator function with BTreeSet?](https://stackoverflow.com/questions/34028324/how-do-i-use-a-custom-comparator-function-with-btreeset) – cafce25 Dec 29 '22 at 00:54
  • 2
    I think its closer to this question of mine: [How to define an ordered Map/Set with a runtime-defined comparator?](/q/65533995/2189130) (which is unfortunately still unanswered) However, I think there's a workaround here to calculate the distance up-front, kept as a third value for each element, and then that can be used solely for the sorting criteria in a custom wrapper. – kmdreko Dec 29 '22 at 02:08
  • @cafce25 No it does not, because I specifically ask about a comparison that depends on runtime data that is not directly encoded in the stored data. – Seriously Dec 29 '22 at 10:20
  • I published [copse](https://crates.io/crates/copse), which ports stdlib's BTree collections to add this functionality. – eggyal Dec 31 '22 at 06:42

1 Answers1

0

I don't think there is a good way to do this without storing the origin along with each point so that you can use it in the Cmp implementation.

use std::cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd};
use std::collections::BTreeSet;

#[derive(Debug, Clone, Copy)]
struct Point2D {
    origin: (f32, f32),
    point: (f32, f32),
}

impl Point2D {
    fn length(self) -> f32 {
        let (x1, y1) = self.origin;
        let (x2, y2) = self.point;
        ((x1 - x2).powi(2) + (y1 - y2).powi(2)).sqrt()
    }
}

impl PartialEq for Point2D {
    fn eq(&self, rhs: &Self) -> bool {
        self.origin == rhs.origin && self.length() == rhs.length()
    }
}

impl Eq for Point2D {}

impl PartialOrd for Point2D {
    fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
        (self.origin == rhs.origin).then_some(self.cmp(rhs))
    }
}

impl Ord for Point2D {
    fn cmp(&self, rhs: &Self) -> Ordering {
        self.length().total_cmp(&rhs.length())
    }
}

fn main() {
    let origin = (1.0, 1.0); // assume values are provided at runtime
    let mut container = BTreeSet::new(); // should be sorted by distance to origin
    container.insert(Point2D {
        origin,
        point: (1.0, 9.0),
    });
    container.insert(Point2D {
        origin,
        point: (2.0, 2.0),
    });
    container.insert(Point2D {
        origin,
        point: (1.0, 5.0),
    });
    println!("{:?}", container.iter().map(|p| p.point).collect::<Vec<_>>());
    // [(2.0, 2.0), (1.0, 5.0), (1.0, 9.0)]
}
BallpointBen
  • 9,406
  • 1
  • 32
  • 62