'How to sort and merge into an iterator two vectors of different structs based on a field?
I need to draw two vectors of objects from back to front based on their distance, but these object are different structs that both have a field for its distance to the camera. How would I sort them to iterate from bigger to smaller distance?
Repro:
struct ItemA{
distance: f32,
}
struct ItemB{
distance: f32,
}
impl ItemA{
fn draw_a(&self) -> {
println!("d: {}", self.distance);
}
}
impl ItemB{
fn draw_b(&self) -> {
println!("d: {}", self.distance);
}
}
fn main() {
let vec_a = vec![ItemA{distance: 1}, ItemA{distance:4}, ItemA{distance:10}];
let vec_b = vec![ItemB{distance: 2}, ItemB{distance:6}, ItemB{distance:7}];
// Should print in order d: 10(a), d: 7(b), d: 6(b), d: 4(a), d: 2(b), d: 1(a)
}
}
Rust playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=a1b58729f60fac8312a2fadc6663c633
Solution 1:[1]
Here is a solution that makes use of an enum to create a type which can store both ItemA or ItemB. This is an alternative to boxing that does not rely on dynamic dispatch.
struct ItemA {
distance: f32,
}
struct ItemB {
distance: f32,
}
impl ItemA {
fn draw_a(&self) {
println!("d: {}(a)", self.distance);
}
}
impl ItemB {
fn draw_b(&self) {
println!("d: {}(b)", self.distance);
}
}
enum AB {
A(ItemA),
B(ItemB),
}
impl AB {
fn dist(&self) -> f32 {
match self {
AB::A(a) => a.distance,
AB::B(b) => b.distance,
}
}
fn draw(&self) {
match self {
AB::A(a) => a.draw_a(),
AB::B(b) => b.draw_b(),
}
}
}
fn main() {
let vec_a = vec![
ItemA { distance: 1.0 },
ItemA { distance: 4.0 },
ItemA { distance: 10.0 },
];
let vec_b = vec![
ItemB { distance: 2.0 },
ItemB { distance: 6.0 },
ItemB { distance: 7.0 },
];
let mut all = vec_a.into_iter().map(|e| AB::A(e)).collect::<Vec<_>>();
all.extend(vec_b.into_iter().map(|e| AB::B(e)));
all.sort_by(|a, b| {
b.dist()
.partial_cmp(&a.dist())
.unwrap_or(std::cmp::Ordering::Equal)
});
// Should print in order d: 10(a), d: 7(b), d: 6(b), d: 4(a), d: 2(b), d: 1(a)
for ab in all {
ab.draw();
}
}
This prints:
d: 10(a)
d: 7(b)
d: 6(b)
d: 4(a)
d: 2(b)
d: 1(a)
Solution 2:[2]
A solution would be to sort both Vec, then explicitly having two indices starting at 0, and printing from either vector at each step, and advancing one index, like in the merge function from mergesort. It would work, but it would be verbose.
A an other solution would be to make a Trait with two methods: a distance(&self) -> f32, which would (in your case) just return self.distance, and a kind(&self) -> &str which would return either "a" or "b", and implement this trait for both ItemA and ItemB. In the end, you can create an homogeneous vector of Box<dyn ThatTrait> and sort that. I mention this solution because I can picture it being a viable solution for a renderer which can have several kind of objects on a scene, pretty much like how graphic toolkits do too in Rust. I think that, in the end, you'll want this solution if you have a medium to big project.
However, the simplest working solution for your example is probably just to extract the wanted information from each vector, and sort that:
fn main() {
let vec_a = vec![ItemA {distance: 1.}, ItemA {distance: 4.}, ItemA {distance: 10.}];
let vec_b = vec![ItemB {distance: 2.}, ItemB {distance: 6.}, ItemB {distance: 7.}];
let mut vec_all = vec_a
.iter()
.map(|ItemA {distance}| distance)
.chain(vec_b
.iter()
.map(|ItemB {distance}| distance)
)
.collect::<Vec<_>>();
vec_all.sort_by(|a, b| a.partial_cmp(b).unwrap());
for d in vec_all {
println!("d: {}", d);
}
}
See the playground.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | BlackBeans |
