How to combine two cmp conditions in `Ord::cmp()` - rust

I'm trying to combine more than one condition when implementing cmp() and eq() from Ord and PartialEq traits. Something that looks like this:
self.id.cmp(&other.id) && self.age.cmp(&other.age)
Here's a working example minus the combined condition:
use std::cmp::Ordering;
#[derive(Debug, Clone, Eq)]
pub struct Person {
pub id: u32,
pub age: u32,
}
impl Person {
pub fn new(id: u32, age: u32) -> Self {
Self {
id,
age,
}
}
}
impl Ord for Person {
fn cmp(&self, other: &Self) -> Ordering {
self.id.cmp(&other.id)
}
}
impl PartialOrd for Person {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Person {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}

Ord doesn't return a boolean, it returns an Ordering which can be Less, Equal or Greater, so you can't just use && on it.
Ordering has a few methods, one of which is then (and its companion then_with), which does the usual "sort by one field, and then by another one" operation. Your example then becomes
fn cmp(&self, other: &Self) -> Ordering {
self.id.cmp(&other.id)
.then(self.age.cmp(&other.age))
}

Related

BTreeMap with comparison function

I would like to create BTreeMap in Rust but to compare my keys I need another data structure which I store in a variable. Unfortunately it seems, that the traits in Rust can't be defined locally and my Ord implementation can't depend on local variables.
Does it mean that I have to reimplement whole BTreeMap to take lambdas?
Though it's not optimal (because of the extra data/pointer you have to store and could mess up) you could store the local variable (or depending on size & usecase a reference to it) alongside your values:
use std::cmp::{PartialOrd, Ord, Ordering};
use std::collections::BTreeSet;
fn main() {
dbg!(by_distance_from(-5));
}
fn by_distance_from(x: i32) -> Vec<i32> {
let v = vec![-5, 0, 1, 3, 10];
struct Comp<'a> {
cmp: &'a Cmp,
v: i32,
}
struct Cmp {
x: i32,
}
let cmp = Cmp { x };
impl PartialOrd for Comp<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.v.abs_diff(self.cmp.x).partial_cmp(&other.v.abs_diff(other.cmp.x))
}
}
impl Ord for Comp<'_> {
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap()
}
}
impl PartialEq for Comp<'_> {
fn eq(&self, other: &Self) -> bool {
self.v == other.v
}
}
impl Eq for Comp<'_> {}
let s: BTreeSet<_> = v.into_iter().map(|v| Comp {
cmp: &cmp,
v,
}).collect();
s.into_iter().map(|Comp { v, ..}| v).collect()
}
Playground

How to write a trait with varying input types for each implementation

I am looking to be able to compare a Card with another Card of the same type. For example an instance of a HarryPotterCard should be able to call its .cmp() method against another instance of a HarryPotterCard.
The issue that I am facing is if I put a HarryPotterCard in the implementation of the cmp function signature for HarryPotterCard it complains that the signatures dont match and if I put a regular Card type then I cannot access the magic and cunning attributes to compare against self
pub struct HarryPotterCard {
name: String,
magic: u32,
cunning: u32,
}
pub struct DinosaurCard {
name: String,
height: f32,
weight: f32,
}
pub trait Card {
fn cmp(&self, other:Box<dyn Card>, field: &str) -> bool;
}
impl Card for DinosaurCard { {
pub fn cmp(&self, other: Box<dyn DinosaurCard>, field: &str) -> bool {
match field {
"height" => self.height > other.height,
"weight" => self.weight > other.weight,
}
}
}
impl Card for HarryPotterCard {
pub fn cmp(&self, other: Box<dyn HarryPotterCard>, field: &str) -> bool {
match field {
"magic" => self.magic > other.magic,
"cunning" => self.cunning > other.cunning,
}
}
}
Since you only want cards to be compared against another of the same type, you probably don't want to use dyn, since that would allow any type of cards to be compared, which is definitely not fair to the poor dinosaurs who have to fight a wizard. You can use Self in the trait declaration, which will limit the other to be the same type as self. To actually implement the trait though, you need to specify it explicitly in your impl block, otherwise it's just a normal method.
pub struct HarryPotterCard {
name: String,
magic: u32,
cunning: u32,
}
pub struct DinosaurCard {
name: String,
height: f32,
weight: f32,
}
pub trait Card {
fn cmp(&self, other:Box<Self>, field: &str) -> bool;
}
impl Card for DinosaurCard {
fn cmp(&self, other: Box<DinosaurCard>, field: &str) -> bool {
match field {
"height" => self.height > other.height,
"weight" => self.weight > other.weight,
_ => panic!("not a real field"),
}
}
}
impl Card for HarryPotterCard {
fn cmp(&self, other: Box<HarryPotterCard>, field: &str) -> bool {
match field {
"magic" => self.magic > other.magic,
"cunning" => self.cunning > other.cunning,
_ => panic!("not a real field"),
}
}
}
Because you aren't able to use trait objects in this scenario (dyn), I would also suggest switching out Box<WhateverCard> with &WhateverCard so that it is a little friendlier to use, as you don't need to heap allocate your data when you call cmp.

How do I insert equal elements into a BTreeMap?

I am working on a project where I need an Ordered Set and I use a BTreeSet for that.
The Elements in my BTreeSet are PItem they are basically just a tuple of an Item and an OrderedFloat, like this: struct PItem((Item, OrderedFloat<f64>)) -> read like struct PItem(Item, weight)
Now the tricky part: My BTreeMap should order the PItems only based on the weight, disregarding the Item.
If my BTreeMap already contains an PItem on the other hand should only be determined by the Item, disregarding the weight. Here is my Implementation for Ord and Eq for Item and PItem:
use ordered_float::OrderedFloat;
use std::cmp::Ordering;
#[derive(Eq)]
pub struct Item{
pub s_i: u32,
pub e_i: u32,
pub val: String,
}
#[derive(Eq)]
pub struct PItem(pub Item, pub OrderedFloat<f64>);
impl Item{
pub fn start(&self) -> u32{
self.s_i
}
pub fn end(&self) -> u32{
self.e_i
}
pub fn val(&self) -> &str{
&self.val
}
}
impl PItem{
pub fn start(&self) -> u32{
self.item().start()
}
pub fn end(&self) -> u32{
self.item().end()
}
pub fn prob(&self) -> &OrderedFloat<f64>{
&(self.1)
}
pub fn val(&self) -> &str{
self.item().val()
}
pub fn item(&self) -> &Item{
return &self.0
}
}
impl PartialOrd for Item {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self.s_i == other.s_i{
if self.e_i == other.e_i{
return self.val.partial_cmp(&(other.val));
}
return self.e_i.partial_cmp(&(other.e_i));
}
return self.s_i.partial_cmp(&(other.s_i));
}
}
impl PartialEq for Item {
fn eq(&self, other: &Self) -> bool {
self.s_i.eq(&(other.s_i)) && self.e_i.eq(&(other.e_i)) && self.val.eq(&(other.val))
}
}
impl Ord for PItem {
fn cmp(&self, other: &PItem) -> Ordering {
if self.prob() == other.prob(){
return self.item().cmp(&(other.item()));
}
return self.prob().cmp(&(other.prob()));
}
}
impl PartialOrd for PItem {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self.prob() == other.prob(){
return self.item().partial_cmp(&(other.item()));
}
return self.prob().partial_cmp(&(other.prob()));
}
}
impl PartialEq for PItem {
fn eq(&self, other: &Self) -> bool {
self.item().eq(&(other.item()))
}
}
impl Ord for Item {
fn cmp(&self, other: &Item) -> Ordering {
if self.start() == other.start(){
if self.end() == other.end(){
return self.val().cmp(&(other.val()));
}
return self.end().cmp(&(other.end()));
}
return self.start().cmp(&(other.start()));
}
}
Now however, I see that I can add the "same" PItem twice to the BTreeMap, when they have different weights. When everything is equal including the weight, I cannot insert it.
What I want is that I already cannot insert it even if the weight is different but the Item's are equal. So apparently it takes the weight into consideration when deciding if an Item is already present in the set. Why is that? I cannot find my mistake in the code. When I check only for equality without adding any PItem to some set, the eq method seems to work fine.

Is it possible to have a binary heap containing different structs in Rust?

I'm working on an event-driven particle simulation. The code below defines the struct Event and implements the equality traits that are needed for a binary heap. This code is working but the object event built by the second constructor (new_event) has redundant member variables. I want to have a binary heap which works with two different event structs in order to avoid this redundancy and improve my code efficiency which is crucial for my project.
Can I achieve this using traits?
pub struct Event
{
pub time: f64,
pub event_type: EventType,
pub particle_index1: usize,
pub particle_index2: usize,
pub particle_count1: usize,
pub particle_count2: usize
}
impl Event
{
pub fn new_collision(time: f64, particle_index1: usize, particle_index2: usize,
particle_count1: usize, particle_count2: usize) -> Self
{
Self {
time,
particle_index1,
particle_index2,
event_type: EventType::EventCollision,
particle_count1,
particle_count2
}
}
pub fn new_event(time: f64, event_type: EventType, particle_index1: usize, particle_count1:
usize) -> Self
{
Self {
time,
particle_index1,
particle_index2: 0, //REDUNDANT
event_type,
particle_count1,
particle_count2: 0 //REDUNDANT
}
}
}
impl PartialEq for Event
{
fn eq(&self, other: &Self) -> bool
{
self.time == other.time
}
}
impl Eq for Event{}
impl PartialOrd for Event
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering>
{
other.time.partial_cmp(&self.time) //reverse order
}
}
impl Ord for Event
{
fn cmp(&self, other: &Self) -> Ordering
{
self.partial_cmp(other).unwrap()
}
}
In this case it would be interesting to use an enum and a combination with a custom trait to avoid cumbersome access to inner enum structs:
use core::cmp::Ordering;
type EventType = String;
trait Timed {
fn time(&self) -> f64;
}
pub struct CollisionEvent {
pub time: f64,
pub particle_index1: usize,
pub particle_index2: usize,
pub particle_count1: usize,
pub particle_count2: usize,
}
pub struct GenericEvent {
pub time: f64,
pub event_type: EventType,
pub particle_index1: usize,
pub particle_count1: usize,
}
pub enum Event {
Collision(CollisionEvent),
Generic(GenericEvent),
}
impl Timed for CollisionEvent {
fn time(&self) -> f64 {
self.time
}
}
impl Timed for GenericEvent {
fn time(&self) -> f64 {
self.time
}
}
impl Timed for Event {
fn time(&self) -> f64 {
let event: &dyn Timed = match self {
Event::Collision(event) => event,
Event::Generic(event) => event,
};
event.time()
}
}
impl Event {
pub fn new_collision(
time: f64,
particle_index1: usize,
particle_index2: usize,
particle_count1: usize,
particle_count2: usize,
) -> Self {
Self::Collision(CollisionEvent {
time,
particle_index1,
particle_index2,
particle_count1,
particle_count2,
})
}
pub fn new_event(
time: f64,
event_type: EventType,
particle_index1: usize,
particle_count1: usize,
) -> Self {
Self::Generic(GenericEvent {
time,
particle_index1,
event_type,
particle_count1,
})
}
}
impl PartialEq for Event {
fn eq(&self, other: &Self) -> bool {
self.time() == other.time()
}
}
impl Eq for Event {}
impl PartialOrd for Event {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
other.time().partial_cmp(&self.time()) //reverse order
}
}
impl Ord for Event {
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap()
}
}
Playground
It is possible with enums to have a create a data type which can represent multiple "types".
So you could do something like:
pub struct Event {
pub time: f64,
pub event_type: EventType,
pub particle_index1: usize,
pub particle_count1: usize,
}
pub struct Collison {
pub time: f64,
pub event_type: EventType,
pub particle_index1: usize,
pub particle_index2: usize,
pub particle_count1: usize,
pub particle_count2: usize
}
pub enum EventOrCollision {
Event(Event),
Collision(Collision)
}
So you could now have a BinaryHeap<EventOrCollision>>. However the EventOrCollision would have to implement PartialOrd and the other type constraints on BinaryHeap. You would need to use pattern matching around this type.
However Rust enums are constant width/size (+1 for the discriminant bit) so the binary heap would take up the same space in memory as you have currently. You would avoid initializing the extra usize fields although that is incredible cheap.

Specialization: PartialEq vs Rc

I'd like to come up with a function that does a best-effort equality check on two values. For T: PartialEq it's easy, and for Rc<T> it's sufficient to use Rc::ptr_eq when T is not PartialEq.
I'm attempting to use specialization to provide the Rc<T: !PartialEq> case but I'm having problems with overlapping implementations, since Rc could implement PartialEq.
Is there any way to come up with a single ref_eq function, with or without specialization?
My code: (playground):
#![feature(specialization)]
use std::fmt::Display;
use std::rc::Rc;
trait RefEq {
fn ref_eq(&self, other: &Self) -> bool;
}
impl<T> RefEq for T {
default fn ref_eq(&self, _other: &Self) -> bool {
false
}
}
impl<T> RefEq for T where T: PartialEq {
default fn ref_eq(&self, other: &Self) -> bool {
self == other
}
}
// error[E0119]: conflicting implementations of trait `RefEq` for type `std::rc::Rc<_>`:
/*
impl<T> RefEq for Rc<T> {
fn ref_eq(&self, other: &Self) -> bool {
Rc::ptr_eq(self, other)
}
}
*/
fn assert_ref_eq<T>() where T: RefEq { }
fn main() {
assert_ref_eq::<f64>(); // f64 is PartialEq => ok
assert_ref_eq::<Rc<String>>(); // String is PartialEq => ok
assert_ref_eq::<Rc<dyn Display>>(); // dyn Display is not PartialEq =>
// please use Rc::ptr_eq
}

Resources