Rust中的trait 之 PartialEq 与 PartialOrd 详解
1.PartialEq
1.1 PartialEq 概述
PartialEq
是 Rust 标准库中的一个基础特性,用于定义“部分相等”关系。它的主要目的是允许我们比较两个值是否相等,但与 Eq 特性不同,PartialEq
允许存在某些值无法进行相等性比较的情况(即返回 false
或其他逻辑)。例如,浮点数(f32和f64)只实现了 PartialEq
,因为它们存在“非数”(NaN
)值,而 NaN
与任何值(包括它自己)的比较结果都是 false
1.2 实现方法
PartialEq 特性要求实现一个方法:
fn eq(&self, other: &Self) -> bool
• &self和 other: &Self :这两个参数分别表示当前实例和另一个要比较的实例的引用。
• 返回值 bool :表示两个实例是否相等。
• 如果两个实例的所有相关字段都相等,则返回true否则返回false。
1.3 代码示例
创建一个 Fruit 结构体,里面的属性有 name(水果的名字)、age(水果的年龄)和 price(水果的价钱),然后把水果的name与age属性当作比较的依据,两个属性值都一样的代表相同
1.3.1 手动实现 PartialEq
fn main() {
let fruit1 = Fruit::new("apple".to_string(), 12, 13);
let fruit2 = Fruit::new("banana".to_string(), 12, 22);
let fruit3 = Fruit::new("apple".to_string(), 12, 11);
if fruit1 == fruit2 {
println!("fruit1 and fruit2 are equal");
};
if fruit1 == fruit3 {
println!("fruit1 and fruit3 are equal");
};
}
struct Fruit{
name:String,
age:u32,
price:u32,
}
impl Fruit{
fn new(name: String, age: u32, price: u32) -> Fruit{
Fruit{
name,
age,
price
}
}
}
impl PartialEq for Fruit{
fn eq(&self, other: &Self) -> bool {
self.name == other.name && self.age == other.age
}
}
打印出的结果是 fruit1 and fruit3 are equal
我们可以自定义比较的规则,比如 self.name == other.name && self.age == other.age
将 && 改成 || 就是其中一个属性相等则相等了
1.3.2 宏实现
也可以在结构体上使用属性宏#[derive(PartialEq)]
#[derive(PartialEq)]
struct Fruit {
name: String,
age: u32,
price: u32,
}
#[derive(PartialEq)]
的作用是让编译器自动生成 PartialEq
特性的实现代码。意味着可以直接使用 ==
和 !=
运算符来比较两个 Fruit 实例是否相等,而无需手动实现 PartialEq
。
自动生成的代码如下
impl PartialEq for Fruit {
fn eq(&self, other: &Self) -> bool {
self.name == other.name && self.age == other.age && self.price == other.price
}
}
需要注意的是,为了能够自动生成 PartialEq 的实现,结构体的每个字段类型都必须实现了 PartialEq 特性。String
和 u32
都实现了 PartialEq 特性。而且#[derive(PartialEq)]
方式实现的是全等匹配,即每个属性都需要一样,则才能判断为相等,如果想实现自定义比较,则可以使用上面的手动实现 PartialEq
如果存在没有实现 PartialEq 特性的属性,则会编译不通过 比如:
fn main() {
let fruit1 = Fruit::new("apple".to_string(), 12, 11,Color { r: 12, g: 13, b: 11,});
let fruit2 = Fruit::new("banana".to_string(), 12, 22,Color { r: 12, g: 13, b: 11,});
let fruit3 = Fruit::new("apple".to_string(), 12, 11,Color { r: 12, g: 13, b: 11,});
if fruit1 == fruit2 {
println!("fruit1 and fruit2 are equal");
};
if fruit1 == fruit3 {
println!("fruit1 and fruit3 are equal");
};
}
#[derive(PartialEq)]
struct Fruit{
name:String,
age:u32,
price:u32,
color: Color, // Color 没有实现 PartialEq
}
struct Color {
r: u8,
g: u8,
b: u8,
}
impl Fruit{
fn new(name: String, age: u32, price: u32,color: Color) -> Fruit{
Fruit{
name,
age,
price,
color
}
}
}
会报这个错
error[E0369]: binary operation `==` cannot be applied to type `Color`
--> src/main.rs:25:5
|
20 | #[derive(PartialEq)]
| --------- in this derive macro expansion
...
25 | color: Color, // Color 没有实现 PartialEq
| ^^^^^^^^^^^^
|
如果想不报错,则需要Color也实现PartialEq
#[derive(PartialEq)]
struct Color {
r: u8,
g: u8,
b: u8,
}
1.4 使用场景
1.4.1 集合操作
在集合操作中,PartialEq
用于判断两个元素是否相等。这在 HashSet
和 HashMap
中尤为重要,因为它们需要通过 PartialEq
来判断元素是否重复或键是否相等。
1. HashSet
HashSet 是一个集合,用于存储唯一元素。PartialEq 用于判断两个元素是否相等,从而避免重复。
示例代码:
use std::collections::HashSet;
fn main() {
let mut fruits = HashSet::new();
fruits.insert(Fruit::new("Apple".to_string(), 5, 3));
fruits.insert(Fruit::new("Apple".to_string(), 5, 3));
println!("Number of unique fruits: {}", fruits.len()); //输出 1
}
#[derive(PartialEq,Eq,Hash)]
struct Fruit{
name:String,
age:u32,
price:u32,
}
impl Fruit{
fn new(name: String, age: u32, price: u32) -> Fruit{
Fruit{
name,
age,
price,
}
}
}
2. HashMap
HashMap 是一个键值对集合,用于存储和查找数据。PartialEq 用于判断键是否相等,从而正确地插入和查找值。
示例代码:
use std::collections::{HashMap};
fn main() {
let mut fruit_map = HashMap::new();
fruit_map.insert(Fruit::new("Apple".to_string(), 5, 3), "Delicious");
if let Some(description) = fruit_map.get(&Fruit { name: "Apple".to_string(), age: 5, price: 3 }) {
println!("Description: {}", description); // 输出 "Delicious"
} else {
println!("Fruit not found");
}
}
#[derive(PartialEq,Eq,Hash)]
struct Fruit{
name:String,
age:u32,
price:u32,
}
impl Fruit{
fn new(name: String, age: u32, price: u32) -> Fruit{
Fruit{
name,
age,
price,
}
}
}
1.4.2 条件判断
1. if 语句
使用 PartialEq 来判断两个值是否相等,并根据结果执行不同的逻辑。
示例代码:
fn main() {
let fruit1 = Fruit { name: "Apple".to_string(), age: 5, price: 3 };
let fruit2 = Fruit { name: "Apple".to_string(), age: 5, price: 3 };
if fruit1 == fruit2 {
println!("fruit1 and fruit2 are equal");
} else {
println!("fruit1 and fruit2 are not equal");
}
}
#[derive(PartialEq)]
struct Fruit{
name:String,
age:u32,
price:u32,
}
2. match 表达式
在 match 表达式中,PartialEq 用于匹配模式。
示例代码:
fn main() {
let fruit = Fruit { name: "Apple".to_string(), age: 5, price: 3 };
match fruit {
Fruit { name: ref n, age: 5, price: 3 } if n == "Apple" => println!("It's an Apple!"),
_ => println!("It's something else"),
}
}
#[derive(PartialEq)]
struct Fruit{
name:String,
age:u32,
price:u32,
}
1.4.3 自定义比较逻辑
在某些情况下,你可能需要自定义比较逻辑。虽然 #[derive(PartialEq)]
提供了默认实现,但你可以手动实现 PartialEq
来满足特定需求。
1. 忽略某些字段
如果在比较 Fruit 时忽略 price 字段。在手动实现 PartialEq 的时候要去掉 #[derive(PartialEq)]
fn main() {
let fruit1 = Fruit { name: "Apple".to_string(), age: 5, price: 3 };
let fruit2 = Fruit { name: "Apple".to_string(), age: 5, price: 4 };
if fruit1 == fruit2 {
println!("fruit1 and fruit2 are equal (ignoring price)");
} else {
println!("fruit1 and fruit2 are not equal");
}
}
struct Fruit {
name: String,
age: u32,
price: u32,
}
impl PartialEq for Fruit {
fn eq(&self, other: &Self) -> bool {
self.name == other.name && self.age == other.age
}
}
2. 特殊比较逻辑
如果在比较 Fruit 时,price 字段的比较允许一定的误差范围。
fn main() {
let fruit1 = Fruit { name: "Apple".to_string(), age: 5, price: 3 };
let fruit2 = Fruit { name: "Apple".to_string(), age: 5, price: 4 };
if fruit1 == fruit2 {
println!("fruit1 and fruit2 are equal (with price tolerance)");
} else {
println!("fruit1 and fruit2 are not equal");
}
}
struct Fruit {
name: String,
age: u32,
price: u32,
}
impl PartialEq for Fruit {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
&& self.age == other.age
&& (self.price as i32 - other.price as i32).abs() <= 1
}
}
1.4.4 单元测试
在单元测试中,PartialEq 用于验证函数或方法的输出是否符合预期。
假设有一个函数 create_fruit ,返回一个 Fruit 实例。你可以使用 PartialEq 来验证返回值是否正确。
fn main() {}
#[derive(PartialEq, Debug)]
struct Fruit {
name: String,
age: u32,
price: u32,
}
fn create_fruit(name: &str, age: u32, price: u32) -> Fruit {
Fruit { name: name.to_string(), age, price }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_fruit() {
let fruit = create_fruit("Apple", 5, 3);
let expected = Fruit { name: "Apple".to_string(), age: 5, price: 3 };
assert_eq!(fruit, expected);
}
}
1.4.5 数据结构中的相等性检查
在复杂的数据结构中,PartialEq 用于判断两个实例是否相等。这在树、图等数据结构中尤为重要
假设有一个二叉树结构,需要判断两棵树是否相等。
fn main() {
let tree1 = Some(Box::new(TreeNode {
value: 1,
left: Some(Box::new(TreeNode { value: 2, left: None, right: None })),
right: Some(Box::new(TreeNode { value: 3, left: None, right: None })),
}));
let tree2 = Some(Box::new(TreeNode {
value: 1,
left: Some(Box::new(TreeNode { value: 2, left: None, right: None })),
right: Some(Box::new(TreeNode { value: 3, left: None, right: None })),
}));
assert_eq!(tree1, tree2);
}
#[derive(Debug)]
struct TreeNode {
value: i32,
left: Option<Box<TreeNode>>,
right: Option<Box<TreeNode>>,
}
impl PartialEq for TreeNode {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
&& self.left == other.left
&& self.right == other.right
}
}
1.4.6 与 Eq 特性结合
Eq 是 PartialEq 的一个子特性,表示“完全相等”关系。Eq 特性用于描述那些所有值都可以进行相等性比较,并且这种比较是自反的、对称的和传递的。换句话说,如果一个类型实现了 Eq ,那么它必须满足以下条件:
- 自反性:任何值与自身比较必须返回 true ,即 a == a 。
- 对称性:如果 a == b ,那么 b == a 。
- 传递性:如果 a == b 且 b == c ,那么 a == c 。
为什么需要 Eq 特性 ?
PartialEq
允许某些值之间无法进行相等性比较(例如浮点数中的 NaN
),而 Eq
则要求所有值都可以进行相等性比较。因此,Eq
提供了更强的保证,使得某些数据结构(如 HashSet
和 HashMap
)可以更高效地工作。
为什么需要同时实现 PartialEq 和 Eq ?
• PartialEq :提供了基本的相等性比较逻辑。
• Eq :提供了更强的保证,确保所有值都可以进行相等性比较,并且这种比较是自反的、对称的和传递的。在某些数据结构中(如 HashSet 和 HashMap ),键类型必须实现 Eq ,因为这些数据结构依赖于键的相等性来判断两个键是否相同。如果键类型只实现了 PartialEq 而没有实现 Eq ,编译器会报错。
自动派生
#[derive(PartialEq, Eq, Hash)]
struct Fruit {
name: String,
age: u32,
price: u32,
}
手动派生
#[derive(Debug)]
struct Fruit {
name: String,
age: u32,
price: u32,
}
impl PartialEq for Fruit {
fn eq(&self, other: &Self) -> bool {
self.name == other.name && self.age == other.age
}
}
impl Eq for Fruit {}
fn main() {
let fruit1 = Fruit { name: "Apple".to_string(), age: 5, price: 3 };
let fruit2 = Fruit { name: "Apple".to_string(), age: 5, price: 4 };
if fruit1 == fruit2 {
println!("fruit1 and fruit2 are equal (ignoring price)");
} else {
println!("fruit1 and fruit2 are not equal");
}
}
注意事项
• 浮点数字段:如果结构体中包含浮点数字段(如 f32 或 f64 ),则不能实现 Eq ,因为浮点数的 NaN 值违反了相等性比较的自反性(NaN != NaN)。
• 自定义逻辑:如果需要自定义比较逻辑,建议手动实现 PartialEq 和 Eq ,并确保逻辑满足 Eq 的要求。
1.4.7 总结
PartialEq 在 Rust 中的使用场景非常广泛,不仅限于集合操作、条件判断等常见场景。它还可以用于序列化和反序列化、状态管理、缓存机制、配置管理、日志记录、用户界面更新、网络通信、游戏开发、并发编程以及命令行工具等多个领域。通过实现 PartialEq ,你可以灵活地定义和比较自定义类型的相等性逻辑,从而满足各种实际需求。
2.PartialOrd
2.1 PartialOrd 概述
PartialOrd
是 Rust 标准库中的一个特性,用于定义“部分有序”关系。它允许比较两个值的大小,但与 Ord 特性不同,PartialOrd
允许某些值之间无法进行比较(即返回 None
)。
2.2 实现方法
PartialOrd 特性要求实现一个方法:
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering>
-
&self 和 other: &Self :这两个参数分别表示当前实例和另一个要比较的实例的引用。
-
返回值 Optionstd::cmp::Ordering:
- Some(Ordering::Less) :表示当前实例小于另一个实例。
- Some(Ordering::Equal) :表示当前实例等于另一个实例。
- Some(Ordering::Greater) :表示当前实例大于另一个实例。
- None :表示无法比较(例如,浮点数中的 NaN 情况)。
说明:
PartialOrd 的目标是定义一个部分有序关系(partial order)。部分有序关系允许某些值之间无法进行有意义的比较,例如浮点数中的 NaN(非数字)。在这种情况下,比较操作可能没有定义,因此返回 None 。
• 部分有序关系的定义:部分有序关系是一种数学概念,它允许某些元素之间无法进行比较。例如,浮点数中的 NaN 无法与其他浮点数进行有意义的比较。 PartialOrd 通过 partial_cmp 方法来实现这种部分有序关系。
• 灵活性:通过返回 Option,partial_cmp 提供了灵活性,允许某些比较操作返回 None,表示无法比较。这使得 PartialOrd 能够处理那些可能没有定义完整有序关系的类型。
使用的过程中,为什么直接调用partial_cmp 进行比较?
在 Rust 中,PartialOrd 的比较操作(如 < 、> 、<= 、>= )最终会调用 partial_cmp 方法。这是因为:
• 统一接口:partial_cmp 是 PartialOrd trait 的核心方法,所有其他比较操作(如 < 、 > 等)都是基于 partial_cmp 的实现。通过调用 partial_cmp,可以确保所有比较操作的行为一致。
• 处理未定义情况:partial_cmp 返回 Option,这使得它能够处理那些可能没有定义比较关系的情况(如 NaN)。如果直接使用 < 、> 等操作符,可能会在某些情况下引发错误或未定义行为,而 partial_cmp 可以明确返回 None,表示无法比较。
2.3 代码示例
2.3.1 手动实现 PartialOrd
use std::cmp::Ordering;
fn main() {
let fruit1 = Fruit { name: "Apple".to_string(), age: 5, price: 3 };
let fruit2 = Fruit { name: "Banana".to_string(), age: 5, price: 4 };
match fruit1.partial_cmp(&fruit2) {
Some(Ordering::Less) => println!("fruit1 < fruit2"),
Some(Ordering::Equal) => println!("fruit1 = fruit2"),
Some(Ordering::Greater) => println!("fruit1 > fruit2"),
None => println!("fruit1 and fruit2 are incomparable")
}
}
#[derive(Debug)]
struct Fruit {
name: String,
age: u32,
price: u32,
}
impl PartialOrd for Fruit{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match self.age.partial_cmp(&other.age) {
Some(Ordering::Equal) => self.price.partial_cmp(&other.price),
other => other,
}
}
}
impl PartialEq for Fruit{
fn eq(&self, other: &Self) -> bool {
self.age == other.age && self.price == other.price
}
}
2.3.2 宏实现
也可以在结构体或枚举上使用属性宏#[derive(PartialOrd, PartialEq)]
fn main() {
let mut fruits = vec![
Fruit { name: "Apple".to_string(), age: 30, price: 170 },
Fruit { name: "Apple".to_string(), age: 30, price: 160 },
Fruit { name: "Banana".to_string(), age: 25, price: 165 },
Fruit { name: "Orange".to_string(), age: 30, price: 160 },
];
// 使用 sort_by 方法对 fruits 进行排序
fruits.sort_by(|a, b| a.partial_cmp(b).unwrap());
// 打印排序后的结果
for fruit in &fruits {
println!("{:?}", fruit);
}
}
#[derive(PartialOrd, PartialEq,Debug)]
struct Fruit {
name: String,
age: u32,
price: u32,
}
#[derive(PartialEq, PartialOrd)]
enum FruitType{
Apple,
Banana,
Orange,
}
运行代码后,输出结果如下:
Fruit { name: "Apple", age: 30, price: 160 }
Fruit { name: "Apple", age: 30, price: 170 }
Fruit { name: "Banana", age: 25, price: 165 }
Fruit { name: "Orange", age: 30, price: 160 }
说明:
• 派生的 PartialOrd 会按照字段的声明顺序进行字典序比较。
• 如果字段是浮点数(如 f64 ),会正确处理 NaN 的情况。
2.3.3 PartialOrd 与 PartialEq
PartialOrd 和 PartialEq 是两个紧密相关的 trait,它们分别用于定义部分有序关系和部分相等关系。
1. 逻辑一致性
PartialOrd 的定义依赖于 PartialEq ,因为相等性是有序性的基础。在数学上,如果两个值是相等的,那么它们在任何有序关系中也应该是相等的。因此, PartialOrd 的实现必须与 PartialEq 的实现保持一致。
2. 语义要求
PartialOrd 的语义要求它能够处理相等的情况。如果一个类型没有定义 PartialEq ,那么 PartialOrd 无法确定两个值是否相等,这会导致逻辑上的不一致。
3. 实现依赖
在 Rust 的标准库中,PartialOrd 的实现依赖于 PartialEq 的实现。具体来说,PartialOrd 的方法 partial_cmp 需要能够调用 PartialEq 的方法 eq 来判断两个值是否相等。
4. 类型安全
Rust 是一种强调类型安全的语言, PartialOrd 和 PartialEq 的关系确保了类型在比较操作中的行为是可预测的。如果一个类型没有实现 PartialEq ,那么在比较时可能会出现逻辑上的混乱。
2.3.4 sort_by 方法
语法
fn sort_by<F>(self: &mut [T], mut compare: F)
where
F: FnMut(&T, &T) -> Ordering,
• self: &mut [T] :表示这是一个可变引用,指向一个可排序的集合(如 Vec 或数组)。
• mut compare: F :表示一个可变闭包,闭包的类型 F 必须满足 FnMut(&T, &T) -> Ordering ,即闭包接受两个 T 类型的引用,并返回一个 Ordering 。
fruits.sort_by(|a, b| a.partial_cmp(b).unwrap());
- fruits:这是一个可排序的集合(例如 Vec ),其中每个元素都实现了 PartialOrd 。
- sort_by :这是一个排序方法,用于对 fruit 进行排序。
- |a, b| a.partial_cmp(b).unwrap():
- a, b| 是一个闭包,a 和 b 是集合中的两个元素的引用。
- a.partial_cmp(b) :调用 partial_cmp 方法比较 a 和 b ,返回 Option 。
- .unwrap() :将 Option 转换为 Ordering 。如果 partial_cmp 返回 None ,unwrap 会触发 panic。
2.4 使用场景
2.4.1 排序
PartialOrd 用于对集合中的元素进行排序。例如,使用 sort_by
方法对向量中的元素进行排序。
use std::cmp::Ordering;
fn main() {
let mut fruits = vec![
Fruit { name: "Apple".to_string(), age: 30, price: 170 },
Fruit { name: "Banana".to_string(), age: 25, price: 165 },
Fruit { name: "Orange".to_string(), age: 30, price: 160 },
];
// 使用 sort_by 方法对 fruits 进行排序
fruits.sort_by(|a, b| a.partial_cmp(b).unwrap());
// 打印排序后的结果
for fruit in &fruits {
println!("{:?}", fruit);
}
}
#[derive(Debug)]
struct Fruit {
name: String,
age: u32,
price: u32,
}
impl PartialEq for Fruit {
fn eq(&self, other: &Self) -> bool {
self.age == other.age && self.price == other.price
}
}
impl PartialOrd for Fruit {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match self.age.partial_cmp(&other.age) {
Some(Ordering::Equal) => self.price.partial_cmp(&other.price),
other => other,
}
}
}
运行代码后,输出结果如下:
Fruit { name: "Banana", age: 25, price: 165 }
Fruit { name: "Orange", age: 30, price: 160 }
Fruit { name: "Apple", age: 30, price: 170 }
2.4.2 条件判断
在条件判断中,PartialOrd
用于比较两个值的大小。例如,在 if 语句或 match 表达式中。
fn main() {
let fruit1 = Fruit { name: "Apple".to_string(), age: 30, price: 170 };
let fruit2 = Fruit { name: "Banana".to_string(), age: 30, price: 165 };
if fruit1 < fruit2 {
println!("fruit1 < fruit2");
} else if fruit1 > fruit2 {
println!("fruit1 > fruit2");
} else {
println!("fruit1 == fruit2");
}
}
//... struct以及PartialEq和PartialOrd实现 见上
运行代码后,输出结果如下:
fruit1 > fruit2
2.4.3 浮点数的比较
浮点数类型(如 f32
和 f64
)实现了 PartialOrd
,因为它们可能包含无法比较的值(如 NaN
)。partial_cmp
方法允许处理这些情况。
fn main() {
let a: f64 = 3.14;
let b: f64 = 2.71;
let c: f64 = f64::NAN;
println!("{:?}", a.partial_cmp(&b)); // Some(Greater)
println!("{:?}", a.partial_cmp(&c)); // None (因为 NaN 无法比较)
println!("{:?}", c.partial_cmp(&c)); // None (NaN 与 NaN 无法比较)
}
运行代码后,输出结果如下:
Some(Greater)
None
None
2.4.4 优先队列
在优先队列中,使用 BinaryHeap 时,需要实现PartialOrd
和 Ord
。
use std::cmp::Ordering;
use std::collections::BinaryHeap;
fn main() {
let mut heap = BinaryHeap::new();
heap.push(Fruit { name: "Apple".to_string(), age: 30, price: 170 });
heap.push(Fruit { name: "Banana".to_string(), age: 25, price: 165 });
heap.push(Fruit { name: "Orange".to_string(), age: 30, price: 160 });
while let Some(fruit) = heap.pop() {
println!("{:?}", fruit);
};
}
#[derive(Debug)]
struct Fruit {
name: String,
age: u32,
price: u32,
}
impl PartialEq for Fruit {
fn eq(&self, other: &Self) -> bool {
self.age == other.age && self.price == other.price
}
}
impl PartialOrd for Fruit {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match self.age.partial_cmp(&other.age) {
Some(Ordering::Equal) => self.price.partial_cmp(&other.price),
other => other,
}
}
}
impl Eq for Fruit {}
impl Ord for Fruit {
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap()
}
}
2.4.5 与 Eq 特性结合
1. 概述
Ord
是一个用于定义全序
关系(total order)的 trait。与 PartialOrd
不同, Ord
要求所有值之间都可以进行比较,并且比较结果是完全确定的。如果一个类型实现了 Ord
,那么它的所有值都可以进行比较,并且比较结果满足以下性质:
• 自反性:a == a
• 反对称性:如果 a <= b 且 b <= a,则 a == b
• 传递性:如果 a <= b 且 b <= c ,则 a <= c
• 全序性:对于任意的 a 和 b, a <= b 或 b <= a 总是成立
Ord 的主要方法是 cmp ,它返回一个 Ordering 枚举值,表示比较结果:
• Ordering::Less :表示 self < other
• Ordering::Equal :表示 self == other
• Ordering::Greater :表示 self > other
2. 为什么要实现 Ord ?
• 排序:使用 sort 或 sort_by 方法对集合进行排序。
• 有序数据结构:使用有序集合(如 BTreeMap 或 BTreeSet)时,键或元素需要实现 Ord 。
• 二分查找:在有序数组中进行二分查找时,需要全序关系。
3. 实现 Ord 的示例
派生实现
对于简单的结构体或枚举,可以使用 #[derive(Ord)] 自动派生 Ord 的实现:
fn main() {
let alice = Person { name: "Alice".to_string(), age: 30 };
let bob = Person { name: "Bob".to_string(), age: 25 };
println!("{:?}", alice.cmp(&bob)); // 输出 Ordering::Greater
}
#[derive(Ord, PartialOrd, PartialEq, Eq)]
struct Person {
name: String,
age: u32,
}
手动实现
use std::cmp::Ordering;
fn main() {
let alice = Person { name: "Alice".to_string(), age: 30 };
let bob = Person { name: "Bob".to_string(), age: 25 };
println!("{:?}", alice.cmp(&bob)); // 输出 Ordering::Greater
}
#[derive(PartialOrd, PartialEq, Eq)]
struct Person {
name: String,
age: u32,
}
impl Ord for Person {
fn cmp(&self, other: &Self) -> Ordering {
self.age.cmp(&other.age).then_with(|| self.name.cmp(&other.name))
}
}
4. 浮点数与 Ord
浮点数(如 f32 和 f64 )不能实现 Ord ,因为它们存在 NaN (非数字)值。NaN 与任何值(包括自身)的比较结果都是未定义的,这违反了 Ord 的全序性要求。
5. 如何处理浮点数
如果需要对包含浮点数的类型进行排序,可以使用以下方法:
• 使用 PartialOrd :浮点数可以实现 PartialOrd ,但比较结果可能是 None。
• 自定义比较逻辑:通过自定义比较逻辑,忽略 NaN 或将 NaN 视为特定值。
use std::cmp::Ordering;
fn main() {
let p1 = Point { x: 1.0, y: 2.0 };
let p2 = Point { x: 1.0, y: 3.0 };
println!("{:?}", p1.cmp(&p2)); // 输出 Ordering::Less
}
#[derive(PartialOrd)]
struct Point {
x: f64,
y: f64,
}
impl Ord for Point {
fn cmp(&self, other: &Self) -> Ordering {
self.x.partial_cmp(&other.x)
.unwrap_or(Ordering::Equal)
.then_with(|| self.y.partial_cmp(&other.y).unwrap_or(Ordering::Equal))
}
}
impl PartialEq for Point {
fn eq(&self, other: &Self) -> bool {
self.x == other.x && self.y == other.y
}
}
impl Eq for Point {}
.unwrap_or(Ordering::Equal)
- 如果 partial_cmp 返回 Some(Ordering) ,unwrap_or 会直接返回这个值。
- 如果 partial_cmp 返回 None ,unwrap_or 会返回 Ordering::Equal 。这意味着如果 x 值无法比较(例如 NaN ),则认为 x 相等。
2.4.6 总结
PartialOrd 是 Rust 中用于定义部分有序关系的 trait,它允许某些值之间无法进行比较。与 Ord 不同, PartialOrd 不要求所有值之间都能进行有意义的比较,这使得它在处理复杂数据类型(如浮点数)时非常灵活。主要应用场景有处理浮点数、自定义数据类型、部分有序集合、排序和比较操作等。