目录
一,std常用数据结构
use std::option;
1,std::option
(1)定义
pub enum Option<T> {
None,
Some(T),
}
这是一个枚举值,要么等于整型的None,要么等于泛型的只含一个元素的元组Some
(2)is_some、is_none
let x:Option<u32>=Some(2);
assert_eq!(x.is_some(),true);
assert_eq!(x.is_none(),false);
let x:Option<u32>=None;
assert_eq!(x.is_some(),false);
assert_eq!(x.is_none(),true);
(3)is_some_and
let x:Option<u32>=Some(2);
assert_eq!(x.is_some_and(|x| x>1),true);
(4)unwrap
当我们确定一个Option值是Some值时,可以直接调用unwrap把Some里面的值取出来。
let p = Some(5);
assert_eq!(p.unwrap(),5);
2,std::result
use std::result;
(1)定义
pub enum Result<T, E> {
Ok(T),
Err(E),
}
这也是个枚举值,要么是等于泛型的元组OK,要么是等于泛型的元组Err
2个模板类型可以一样也可以不一样。
(2)is_ok、is_err
let x:Result<i32,i32>=Ok(1);
assert_eq!(x.is_ok(),true);
assert_eq!(x.is_err(),false);
let x:Result<i32,&str>=Err("456");
assert_eq!(x.is_ok(),false);
assert_eq!(x.is_err(),true);
str类型要带取址符。
(3)is_ok_and、is_err_and
let x:Result<i32,i32>=Ok(1);
assert_eq!(x.is_ok_and(|x| x==1),true);
assert_eq!(x.is_err(),false);
let x:Result<i32,&str>=Err("456");
assert_eq!(x.is_ok(),false);
assert_eq!(x.is_err_and(|x| x=="456"),true);
(4)ok、err
用ok、err可以直接取出2个成员值,有一个成员会是Some值,另一个会是None值。
let x:Result<i32,i32>=Ok(1);
assert_eq!(x.ok(),Some(1));
assert_eq!(x.err(),None);
let x:Result<i32,&str>=Err("456");
assert_eq!(x.ok(),None);
assert_eq!(x.err(),Some("456"));
(5)unwrap
let x:Result<i32,i32>=Ok(1);
assert_eq!(x.unwrap(),1);
assert_eq!(x.ok().unwrap(),1);
(6)map
源码:
pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> Result<U, E> {
match self {
Ok(t) => Ok(op(t)),
Err(e) => Err(e),
}
}
示例:
fn main() {
let x:Result<i32,i32>=Ok(1);
let x=x.map(|x| x+1);
assert_eq!(x,Ok(2));
let x:Result<i32,i32>=Err(1);
let x=x.map(|x| x+1);
assert_eq!(x,Err(1));
print!("end ");
}
因为Result的map方法是只对Ok生效的
3,字符串
(0)字符串编码
参考这里
(1)String的实现
use std::string::String;
pub struct String {
vec: Vec<u8>,
}
(2)String和Vec<char>互转
String和Vec<char>直接互转,该映射是双射,不会失败
let s=String::from("1234");
let v:Vec<char>=s.chars().collect();
assert_eq!(v.len(),4);
let s:String = v.into_iter().collect();
assert_eq!(s,String::from("1234"));
整数转字符串:
fn int_to_string(x:i32)->String{
let mut ans = String::new();
let mut v=Vec::new();
let mut x = x;
let mut c='0';
while x>0 {
v.insert(0,(c as u8 + (x%10) as u8)as char);
x/=10;
}
return v.into_iter().collect();
}
字符串转整数
fn string_to_int(v:& Vec<char>)->i32{
let mut id :usize=0;
let mut flag:i32 = 1;
if v[id] == '-' {
id+=1;
flag = -1;
}
let mut s:i32 = 0;
while id < v.len() {
s = s * 10 + v[id].to_digit(10).unwrap() as i32;
id+=1;
}
return s*flag;
}
(3)String和Vec<u8>互转
String和Vec<u8>互转, String转Vec<u8> 是单射但不是满射,所以from_utf8返回值是Result
let s=String::from("哈喽,世界");
let v:Vec<u8> = s.into_bytes();
let s = String::from_utf8(v).unwrap();
assert_eq!(s,"哈喽,世界");
(4)&str的实现
str是内置类型,表示[u8],即u8类型的切片。
由于切片类型没有固定的size,不能用于栈中,所以我们一般用的都是&str
字符串字面量的引用也是&str
fn get_max_char(s:&str) -> char{
let mut ans:char = s.chars().next().unwrap();
for x in s.chars(){
if ans < x {
ans = x;
}
}
return ans;
}
fn main() {
let s:&str = "hello world";
assert_eq!(get_max_char(s), 'w');
println!("end");
}
(5)&str和String互转
完整切片:String和&str是双射
let a:&str = "hello world";
let b:String = String::from(a);
let c:&str = &b;
let d:&str = &b[..];
部分引用切片:可能panic
let s: String = String::from("猜猜我是谁");
let b: &str = &s[0..6];
assert_eq!(b,"猜猜");
let c: &str = &s[1..6]; // thread panicked
也就是说,str和String一样,虽然底层实现是u8组成的,但和u8序列并不双射,而是和字符串双射。
(6)&str和Vec<char>互转
let s:&str = "hello world";
let v:Vec<char>=s.chars().collect();
let s2:String = v.into_iter().collect();
let s3:&str=&s2;
(7)&str和Vec<u8>互转
let s:&str = "hello world";
let v:Vec<u8> = s.as_bytes().to_vec();
let s2 = String::from_utf8(v).unwrap();
let s3:&str=&s2;
(8)完整互转有向图
左边2个构成u8域,右边3个构成char域,域内双射,从char域到u8域是单射非满射。
二,std容器
三,自定义数据结构
1,单链表(力扣版)
链表定义:
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct ListNode {
pub val: i32,
pub next: Option<Box<ListNode>>
}
impl ListNode {
#[inline]
fn new(val: i32) -> Self {
ListNode {
next: None,
val
}
}
}
单链表和vector的互转:
fn trans_link_list_to_vector(head: Option<Box<ListNode>>)->Vec<i32>{
let mut v=Vec::new();
if head.is_none(){
return v;
}
let mut p = & mut head.unwrap();
v.push(p.val);
loop{
if let Some(ref mut x) = p.next {
v.push(x.val);
p=x;
}else{
break;
}
}
return v;
}
fn trans_vector_to_link_list(v:Vec<i32>)-> Option<Box<ListNode>> {
let mut ans = Box::new(ListNode::new(0));
let mut p =&mut ans;
for i in 0..v.len() {
p.next = Some(Box::new(ListNode::new(v[i])));
if let Some(ref mut x) = p.next{
p=x;
}
}
return ans.next;
}
2,二叉树(力扣版)
二叉树定义:
use std::rc::Rc;
use std::cell::RefCell;
#[derive(Debug, PartialEq, Eq)]
pub struct TreeNode {
pub val: i32,
pub left: Option<Rc<RefCell<TreeNode>>>,
pub right: Option<Rc<RefCell<TreeNode>>>,
}
impl TreeNode {
#[inline]
pub fn new(val: i32) -> Self {
TreeNode {
val,
left: None,
right: None
}
}
}
序列化和反序列化:
fn serialize(root: Option<Rc<RefCell<TreeNode>>>, error: i32)->Vec<i32> {
let mut q=VecDeque::new();
q.push_back(root.clone());
let mut ans = Vec::new();
while q.len() > 0{
let p = q.pop_front().unwrap();
if p.is_none() {
ans.push(error);
continue;
}
let r = p.clone().unwrap();
ans.push(r.borrow_mut().val);
q.push_back(r.borrow_mut().left.clone());
q.push_back(r.borrow_mut().right.clone());
}
return ans;
}
fn deserialize(v:Vec<i32>, error:i32) ->Option<Rc<RefCell<TreeNode>>>{
if v[0]==error {
return None;
}
let mut id:usize = 0;
let val = v[id];
id+=1;
let root = Some(Rc::new(RefCell::new(TreeNode::new(val))));
let mut q=VecDeque::new();
q.push_back(root.clone());
while q.len() > 0 {
let p = q.pop_front().unwrap();
let r = p.clone().unwrap();
let val = v[id];
id+=1;
if val != error {
r.borrow_mut().left = Some(Rc::new(RefCell::new(TreeNode::new(val))));
q.push_back(r.borrow_mut().left.clone());
}
let val = v[id];
id+=1;
if val != error {
r.borrow_mut().right = Some(Rc::new(RefCell::new(TreeNode::new(val))));
q.push_back(r.borrow_mut().right.clone());
}
}
root
}
四,std::cmp
1,min函数、max函数
std::cmp::min
std::cmp::max
2,Reverse
这是一个泛型的元组结构体,实现了泛型的逆序:
pub struct Reverse<T>(#[stable(feature = "reverse_cmp_key", since = "1.19.0")] pub T);
#[stable(feature = "reverse_cmp_key", since = "1.19.0")]
impl<T: PartialOrd> PartialOrd for Reverse<T> {
#[inline]
fn partial_cmp(&self, other: &Reverse<T>) -> Option<Ordering> {
other.0.partial_cmp(&self.0)
}
#[inline]
fn lt(&self, other: &Self) -> bool {
other.0 < self.0
}
#[inline]
fn le(&self, other: &Self) -> bool {
other.0 <= self.0
}
#[inline]
fn gt(&self, other: &Self) -> bool {
other.0 > self.0
}
#[inline]
fn ge(&self, other: &Self) -> bool {
other.0 >= self.0
}
}
#[stable(feature = "reverse_cmp_key", since = "1.19.0")]
impl<T: Ord> Ord for Reverse<T> {
#[inline]
fn cmp(&self, other: &Reverse<T>) -> Ordering {
other.0.cmp(&self.0)
}
}
五,std算法、自实现算法
1,排序
(0)sort 和 sort_unstable
sort是Timsort,sort_unstable是pdqsort,参考常见排序算法
(1)vector排序
fn main() {
let mut nums=vec![1,2,4,3];
nums.sort();
println!("{}",nums[3]);
}
vector的默认排序,从小到大排序,输出4
(2)自定义排序
给自定义结构体添加自定义排序规则,需要实现Ord特征
结构体实现:
#[derive(Eq,PartialEq)]
struct Node{
id:i32,
num:i32
}
impl Ord for Node {
#[inline]
fn cmp(&self, other: &Node) -> Ordering {
if self.num != other.num{
return (*other).num.cmp(&self.num);
}else{
return self.id.cmp(&(*other).id);
}
}
}
impl PartialOrd for Node{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
return Some(self.cmp(other));
}
}
Ordering是枚举值,分别表示小于等于大于。
形如self.cmp(other)的排序规则是按照数据类型本身的排序规则,基本类型默认是升序。
形如other.cmp(slef)的排序规则则是反过来的顺序。
调用代码:
let mut v:Vec<Node>=Vec::new();
......
v.sort_by(|a,b| a.cmp(b));
注意,自己实现Ord但是PartialOrd用默认的话,在排序里面的逻辑也是对的,但是以后如果有别人把这个结构体用于排序之外的场景,可能就有BUG了。
2,二分
(1)vector二分
fn search(nums: &Vec<i32>, target:i32)->i32{
let res = nums.binary_search(&target);
match res{
Ok(x)=>x as i32,
_=>-1
}
}
fn main() {
let nums =vec![1,2,4,5];
println!("{}",search(&nums,1));
println!("{}",search(&nums,4));
println!("{}",search(&nums,7));
}
输出:
0
2
-1
非排序数组调用binary_search也能运行,但可能找不到结果。
降序数组和非排序数组同理。
有重复数的情况下,找到任意一个就直接结束。
(2)自定义二分
struct SearchData{
target:i32
}
impl SearchData{
fn find(&self, low:i32,high:i32)-> i32{
let mut low = low;
let mut high = high;
if self.check_value(high) == false{
return high+1;
}
if self.check_value(low) == true{
return low;
}
while high - low > 1 {
let mid = (high + low)/2;
if self.check_value(mid) == true{
high = mid;
}
else {
low = mid;
}
}
return high;
}
fn check_value(&self,x:i32)->bool{
if x*x*x>self.target{
return true;
}
return false;
}
}
fn main() {
let x=SearchData{target:100};
println!("{}",x.find(0,50));
}
输出5,即5是满足x*x*x>target的最小值。
3,哈希算法
rust提供的哈希方案,是把任意数据结构转换成u8整数序列,而整数序列再次哈希的方案一般就采用默认方案。
(1)std::hash::Hash
pub trait Hash {
fn hash<H: Hasher>(&self, state: &mut H);
fn hash_slice<H: Hasher>(data: &[Self], state: &mut H) where
Self: Sized,
{
for piece in data {
piece.hash(state)
}
}
}
其中是hash函数,就是往state里面依次注册整数。
state是一个具有Hasher特征的H类型的数据。
hash_slice就是调用hash函数,把数组的每个成员,依次转换成整数注册到state里面。
示例:
struct OrderAmbivalentPair<T: Ord>(T, T);
impl<T: Ord + Hash> Hash for OrderAmbivalentPair<T> {
fn hash<H: Hasher>(&self, hasher: &mut H) {
min(&self.0, &self.1).hash(hasher);
max(&self.0, &self.1).hash(hasher);
}
}
(2)std::hash::Hasher
pub trait Hasher {
fn finish(&self) -> u64;
fn write(&mut self, bytes: &[u8]);
fn write_u8(&mut self, i: u8) {
self.write(&[i])
}
......
}
Hasher中提供各种类型数据的write***函数,其他的wtite***函数也都是调用write函数,最终都是生成一个u8的整数序列。
最后再调用finish函数即可得到一个u64的整数。
(3)Hash中的hash函数
常见类型的hash函数:
macro_rules! impl_write {
($(($ty:ident, $meth:ident),)*) => {$(
#[stable(feature = "rust1", since = "1.0.0")]
impl Hash for $ty {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
state.$meth(*self)
}
#[inline]
fn hash_slice<H: Hasher>(data: &[$ty], state: &mut H) {
let newlen = mem::size_of_val(data);
let ptr = data.as_ptr() as *const u8;
// SAFETY: `ptr` is valid and aligned, as this macro is only used
// for numeric primitives which have no padding. The new slice only
// spans across `data` and is never mutated, and its total size is the
// same as the original `data` so it can't be over `isize::MAX`.
state.write(unsafe { slice::from_raw_parts(ptr, newlen) })
}
}
)*}
}
impl_write! {
(u8, write_u8),
(u16, write_u16),
(u32, write_u32),
(u64, write_u64),
(usize, write_usize),
(i8, write_i8),
(i16, write_i16),
(i32, write_i32),
(i64, write_i64),
(isize, write_isize),
(u128, write_u128),
(i128, write_i128),
}
#[stable(feature = "rust1", since = "1.0.0")]
impl Hash for bool {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u8(*self as u8)
}
}
#[stable(feature = "rust1", since = "1.0.0")]
impl Hash for char {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u32(*self as u32)
}
}
#[stable(feature = "rust1", since = "1.0.0")]
impl Hash for str {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_str(self);
}
}
(4)BuildHasher
相当于Hasher的再次包装
pub trait BuildHasher {
/// Type of the hasher that will be created.
#[stable(since = "1.7.0", feature = "build_hasher")]
type Hasher: Hasher;
#[stable(since = "1.7.0", feature = "build_hasher")]
fn build_hasher(&self) -> Self::Hasher;
#[stable(feature = "build_hasher_simple_hash_one", since = "1.71.0")]
fn hash_one<T: Hash>(&self, x: T) -> u64
where
Self: Sized,
Self::Hasher: Hasher,
{
let mut hasher = self.build_hasher();
x.hash(&mut hasher);
hasher.finish()
}
}
(5)特征一致性
Hash特征应该和PartialEq特征保持一致,即当a==b时hash(a)==hash(b)
所以在自定义等价关系时,等价的2个数据转换后的u8序列也应该相同。
4,BFS
以有向图多起点BFS洪泛为例:
//有向图多起点BFS洪泛,输入起点集v和邻接表m_next,输出经过可达的所有节点集v
//m_next的key是完整的, value可以是空列表
fn bfs(v:& mut Vec<i32>, m_next:& mut HashMap<i32,Vec<i32>>)-> (){
let mut m=HashMap::new();
let mut deq=VecDeque::new();
for i in 0..v.len(){
m.insert(v[i], 1);
deq.push_back(v[i]);
}
loop {
if deq.is_empty(){
break;
}
let id = deq.pop_front().unwrap();
for i in m_next.get_mut(&id).unwrap(){
if m.get_mut(&i) == None{
v.push(*i);
m.insert(*i, 1);
deq.push_back(*i);
}
}
}
}
5,并查集
一个并查集的示例:
struct Union{
fa:Vec<usize>,
}
impl Union{
fn find(& mut self,x:usize)->usize{
if self.fa[x] == x{
return x;
}
Union::find(self,self.fa[x]);
self.fa[x] = self.fa[self.fa[x]];
return self.fa[x];
}
fn in_same(& mut self,x:usize,y:usize)->bool{
return Union::find(self,x) == Union::find(self,y);
}
fn merge(& mut self,x:usize,y:usize)->(){
Union::find(self,x);
let fax = self.fa[x];
self.fa[fax] = y;
}
}
一个带权并查集的示例:
struct Union{
fa:Vec<usize>,
dif:Vec<i32>,
}
impl Union{
fn find(& mut self,x:usize)->usize{
if self.fa[x] == x{
return x;
}
Union::find(self,self.fa[x]);
self.dif[x] += self.dif[self.fa[x]];
self.fa[x] = self.fa[self.fa[x]];
return self.fa[x];
}
fn in_same(& mut self,x:usize,y:usize)->bool{
return Union::find(self,x) == Union::find(self,y);
}
fn merge(& mut self,x:usize,y:usize,x_sub_y:i32)->(){
Union::find(self,x);
let fax = self.fa[x];
self.dif[fax] = x_sub_y - self.dif[x];
self.fa[fax] = y;
}
}
六,通用函数
1,new函数
除了智能指针之外,其他的new都是创建空对象。
let x=Box::new(1);
let x=RefCell::new(3);
let x=Rc::new(4);
let x:Vec<i32>=Vec::new();
let x:VecDeque<i32>=VecDeque::new();
let x:LinkedList<i32>=LinkedList::new();
let x=String::new();
2,from
对于整数类型,from可以往更大的类型转,不能往更小的类型转。
let x=i32::from(1);
let y=i64::from(x);
let z=i64::from(y);
let nums=Vec::from([1,2,4,3]);
let deq = VecDeque::from([-1, 0, 1]);
let list = LinkedList::from([1,2,4,3]);
let s=String::from("123");
七,二元关系特征
在c++的stl中,只需要2个关系,分别可以用<和==表示,其中<是序关系,==是等价关系。
rust中的二元关系更多更细致,我们一个个分析。
0,继承、一致性大前提
Ord继承了PartialOrd和Eq,而PartialOrd和Eq都继承了PartialEq
这是一个钻石环,不过trait没有构造和析构过程,所以也不在乎钻石环。
由于继承关系的存在,父特征和子特征可以对同一关系做不同的实现,但这是一种很不好的写法。
rust建议一:保持父特征和子特征中二元关系的一致性,是一个默认的大前提。
PS:本章列举的rust建议,都是强烈建议,不遵守的代码可能可以编译运行,可能逻辑也暂时是对的,但一定不是好代码。
1,std::cmp::PartialEq、lhs、rhs
rust建议二:PartialEq满足对称性、传递性,即PartialEq就是部分等价关系。
rust建议三:eq和ne保持对偶,即永远不要重写ne函数
eq就是==,ne就是!=
pub trait PartialEq<Rhs: ?Sized = Self> {
/// This method tests for `self` and `other` values to be equal, and is used
/// by `==`.
#[must_use]
#[stable(feature = "rust1", since = "1.0.0")]
fn eq(&self, other: &Rhs) -> bool;
/// This method tests for `!=`. The default implementation is almost always
/// sufficient, and should not be overridden without very good reason.
#[inline]
#[must_use]
#[stable(feature = "rust1", since = "1.0.0")]
fn ne(&self, other: &Rhs) -> bool {
!self.eq(other)
}
}
lhs是left-hand side,rhs是right-hand side
PartialEq的泛型参数是rhs,默认是self,意思是默认和同类型数据进行比较。
如果用于不同类型的比较,要想满足对称性,2个PartialEq特征都要实现,且需要保持逻辑一致(对偶)。
2,std::cmp::Eq
rust建议四:Eq满足自反性、对称性、传递性,即Eq就是等价关系。
This means, that in addition to a == b and a != b being strict inverses, the equality must be (for all a, b and c):
- reflexive: a == a;
- symmetric: a == b implies b == a; and
- transitive: a == b and b == c implies a == c.
pub trait Eq: PartialEq<Self> {
// this method is used solely by #[deriving] to assert
// that every component of a type implements #[deriving]
// itself, the current deriving infrastructure means doing this
// assertion without using a method on this trait is nearly
// impossible.
//
// This should never be implemented by hand.
#[doc(hidden)]
#[no_coverage] // rust-lang/rust#84605
#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
fn assert_receiver_is_total_eq(&self) {}
}
例如:
f32就是满足部分等价关系,如果把f32里面的NAN去掉,得到的就是等价关系。
assert_ne!(NAN, NAN);
我们很容易有2个疑问:
(1)从源码可以看出来,Eq是非泛型的,PartialEq是泛型的,为什么这么设计呢?
因为不同类型的2个元素进行比较,和自反性无关,需要声明自反性的场景一定是同类型数据进行比较的场景。相当于PartialEq泛型是为了更好用,Eq非泛型是为了更简洁。
(2)Eq和PartialEq的方法是一样的,都是==和!=2个运算符,为什么还要区分2个特征呢?
因为==运算符无法说明自身是等价关系还是部分等价关系,当我们把PartialEq实现成部分等价关系时,建议Eq是不存在的,当我们把PartialEq实现成等价关系时,建议把默认Eq也声明出来。
3,std::cmp::PartialOrd
pub trait PartialOrd<Rhs: ?Sized = Self>: PartialEq<Rhs> {
/// This method returns an ordering between `self` and `other` values if one exists.
#[must_use]
#[stable(feature = "rust1", since = "1.0.0")]
fn partial_cmp(&self, other: &Rhs) -> Option<Ordering>;
/// This method tests less than (for `self` and `other`) and is used by the `<` operator.
#[inline]
#[must_use]
#[stable(feature = "rust1", since = "1.0.0")]
fn lt(&self, other: &Rhs) -> bool {
matches!(self.partial_cmp(other), Some(Less))
}
/// This method tests less than or equal to (for `self` and `other`) and is used by the `<=`
#[inline]
#[must_use]
#[stable(feature = "rust1", since = "1.0.0")]
fn le(&self, other: &Rhs) -> bool {
matches!(self.partial_cmp(other), Some(Less | Equal))
}
/// This method tests greater than (for `self` and `other`) and is used by the `>` operator.
#[inline]
#[must_use]
#[stable(feature = "rust1", since = "1.0.0")]
fn gt(&self, other: &Rhs) -> bool {
matches!(self.partial_cmp(other), Some(Greater))
}
/// This method tests greater than or equal to (for `self` and `other`) and is used by the `>=`
#[inline]
#[must_use]
#[stable(feature = "rust1", since = "1.0.0")]
fn ge(&self, other: &Rhs) -> bool {
matches!(self.partial_cmp(other), Some(Greater | Equal))
}
}
partial_cmp有4种结果,分别是Ordering的3个枚举值,和None,表示无法比较顺序。
PartialOrd的另外4个函数,默认实现都是直接调用partial_cmp得到的,分别是< <= > >=
从源码注释和官方文档可以看到rust建议五:PartialOrd应该满足如下三条特性:
- irreflexivity of < and >: !(a < a), !(a > a)
- transitivity of >: if a > b and b > c then a > c
- duality of partial_cmp: partial_cmp(a, b) == partial_cmp(b, a).map(Ordering::reverse)
4,PartialOrd的二元关系推导
由于这一块内容比较复杂,所以我单独写成一节。
(0)一致性大前提
partial_cmp返回Equal 等价于 父类的eq返回true
partial_cmp返回另外3个值 等价于 父类的eq返回false
所以,==关系具有对称性、传递性
(1)对偶性
PartialOrd满足 对偶性,即a<b等价于b>a a==b等价于b==a a>b等价于b<a
(2)反自反
PartialOrd满足 大于关系的反自反、小于关系的反自反
(3)传递性
PartialOrd满足 大于关系的传递性
通过对偶性和大于关系的传递性,可以推出小于关系也具有传递性。
(4)对称性
通过反自反和传递性可以推出,大于关系和小于关系都具有反对称性,不具有对称性。
根据源码和官方文档,只能推导出上面的这些信息,总结起来就是两句话:
PartialOrd中的 < 和 > 都是拟序,且2个关系互为对偶,且都和部分等价关系没有交集。
PartialOrd中的 <= 和 >= 指的是对应的拟序关系和部分等价关系的并集,<= 和 >=互为对偶。
实际上这些条件是不够的,里面仍然包含了一些非常复杂的情况。
(5)关系之间的传递性
根据实际情况,我认为实际上是有rust建议六:partial_cmp最好满足如下4条传递性:
- 若a>b b==c则a>c
- 若a<b b==c则a<c
- 若a==b b>c则a>c
- 若a==b b<c则a<c
根据前面的性质和新增的这4条传递性,可以推出<=和>=也具有传递性。
(6)等价自反子集
等价自反子集:由满足a==a的所有的a构成的集合。
例如,对于float集合,去掉NAN得到的就是等价自反子集。
显然,对于一个具有部分等价关系的集合,它的等价自反子集一定具有等价关系。
(7)非自反元素的隔离
rust建议七:如果a!=a,则a和任意元素的partial_cmp都返回None。
例如,f32类型就是满足建议六和建议七的。
(8)等价自反子集的映射子集上的偏序
实际上,即使加上了建议六和建议七,<=仍然不是等价自反子集上的偏序关系。
示例:
#[derive(Eq,PartialEq,Ord)]
struct Node{
x:i32,
y:i32,
}
impl PartialOrd for Node {
#[inline]
fn partial_cmp(&self, other: &Node) -> Option<Ordering> {
if self.x < (*other).x {
return Some(Ordering::Less);
}
if self.x > (*other).x {
return Some(Ordering::Greater);
}
if self.y <= 0 && (*other).y <= 0 {
return Some(Ordering::Equal);
}
if self.y > 0 && (*other).y > 0 {
return Some(Ordering::Equal);
}
return None;
}
}
这还是一个比较简单的例子,还有很多更复杂的例子都满足上面的所有性质和建议。
一般情况下,在讨论自反性时,我们说a和b是同一个元素,指的是a和b是同一个内存地址中的数据,满足等价关系或者完全相等的数值并不代表是同一个元素。
所以,我们首先把等价自反子集做一个映射,把所有等价的元素映射到一个元素上,得到等价自反子集的映射子集。
这样,我们就可以说,在等价自反子集的映射子集上,PartialOrd中的<=是偏序。
(9)示例
#[derive(Eq,PartialEq,Ord)]
struct Node2{
x:i32,
y:i32,
}
impl PartialOrd for Node2 {
#[inline]
fn partial_cmp(&self, other: &Node2) -> Option<Ordering> {
if self.x == 0 || (*other).x == 0{
return None;
}
if self.x < (*other).x {
return Some(Ordering::Less);
}
if self.x > (*other).x {
return Some(Ordering::Greater);
}
if self.y <= 0 && (*other).y <= 0 {
return Some(Ordering::Equal);
}
if self.y > 0 && (*other).y > 0 {
return Some(Ordering::Equal);
}
return None;
}
}
在这个例子中,集合是Node2的定义域,即全体平面{(x,y) | x是整数,y是整数}。这里我们忽略i32的上下界,把它理解成全体整数。
等价自反子集是{(x,y) | x是整数,x!=0,y是整数}
等价自反子集的映射子集是{(x,z) | x是整数,x!=0,z=0或1},映射方式是y<=0则z=0,y>0则z=1
那么Node2::partial_cmp在这个映射子集上的关系就是偏序。
(10)不同类型数据之间的PartialOrd
PartialOrd和PartialEq类似,支持泛型。
如果用于不同类型的比较,要想满足对称性,2个PartialEq特征都要实现,且需要保持逻辑一致(对偶)。
5,std::cmp::Ord
pub trait Ord: Eq + PartialOrd<Self> {
#[must_use]
#[stable(feature = "rust1", since = "1.0.0")]
fn cmp(&self, other: &Self) -> Ordering;
......
}
(1)数据类型、泛型
和Eq类似,Ord不支持泛型,即Ord只用于比较同类型数据。
(2)cmp的二元关系
(2.1)for all `a`, `b` and `c`: exactly one of `a < b`, `a == b` or `a > b` is true; 这个是强制要求,因为cmp的返回值类型是Ordering,而不是Option。
(2.2)根据rust建议一:保持父特征和子特征中二元关系的一致性,可以推出一个恒等式:partial_cmp(a, b) == Some(cmp(a, b)),这也是源码和官方文档中给出的式子。
(2.3)根据(2.1)和(2.2)以及partial_cmp本身蕴含的诸多条件,可以推出,cmp中的<就是严格弱序,<=就是全序。
(3)PartialOrd和Ord的选择
上文提到:当我们把PartialEq实现成部分等价关系时,建议Eq是不存在的,当我们把PartialEq实现成等价关系时,建议把默认Eq也声明出来。
PartialOrd和Ord也是类似的,当我们把PartialOrd(中的<=)实现成全序时,建议把Ord也实现出来,否则,建议Ord是不存在的。
PS:为什么不给cmp添加默认实现,由partial_cmp推导出cmp呢?参考下文,实际上应该用cmp推导出partial_cmp,所以2个函数都没有默认实现。
6,默认序关系
(1)基本类型
整数类型都有默认的全序关系,1<=2<=3
浮点数有默认的偏序关系,1<=1.5<=2
其他基本类型同理。
(2)结构体的默认序关系
如果结构体用derive声明默认的PartialOrd或者Ord,则按照字典序推导出序关系。
当然,能编译通过的前提是所有成员都有相应的序关系。
(3)容器的默认序关系
以vec为例,默认序关系就是字典序:
/// Implements comparison of vectors, [lexicographically](Ord#lexicographical-comparison).
#[stable(feature = "rust1", since = "1.0.0")]
impl<T, A1, A2> PartialOrd<Vec<T, A2>> for Vec<T, A1>
where
T: PartialOrd,
A1: Allocator,
A2: Allocator,
{
#[inline]
fn partial_cmp(&self, other: &Vec<T, A2>) -> Option<Ordering> {
PartialOrd::partial_cmp(&**self, &**other)
}
}
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: Eq, A: Allocator> Eq for Vec<T, A> {}
/// Implements ordering of vectors, [lexicographically](Ord#lexicographical-comparison).
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: Ord, A: Allocator> Ord for Vec<T, A> {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
Ord::cmp(&**self, &**other)
}
}
7,自定义序关系的场景
本节内容只讨论同类型数据之间的关系处理原则,不同类型之间没什么要注意的。
(1)PartialEq和Eq
正常情况下,我们都应该实现PartialEq特征,而Eq特征虽然继承了PartialEq但并没有自己的方法需要实现。
(2)偏序的PartialOrd
如果实现一个偏序,则只实现PartialOrd,无论把PartialOrd实现成什么样,都建议Ord是不存在的。
比如,这里我实现了PartialOrd,又声明了默认的Ord,则会造成不一致:
#[derive(Eq,PartialEq,Ord)]
struct Node{
num:i32
}
impl PartialOrd for Node {
#[inline]
fn partial_cmp(&self, other: &Node) -> Option<Ordering> {
if self.num == 0 || (*other).num == 0 {
return None;
}
return (*other).num.partial_cmp(&self.num);
}
}
fn main() {
let x = Node{num:1};
let y = Node{num:3};
assert_eq!(x.partial_cmp(&y),Some(Ordering::Greater));
assert_eq!(x.cmp(&y),Ordering::Less);
println!("end");
}
(3)全序的PartialOrd和Ord
当我们把PartialOrd实现成去全序时,建议一律采用如下的固定实现方式,即只调用cmp推导出partial_cmp。
impl PartialOrd for Node {
#[inline]
fn partial_cmp(&self, other: &Node) -> Option<Ordering> {
return Some(self.cmp(other));
}
}
例如,全序的二叉堆,虽然源码只会调用<=函数,即只会调用partial_cmp而不会调用cmp,但是二叉堆的数据类型必须是含有Ord特征的类型,f32就编译不过。
因为,有Ord特征就暗示了是全序关系,PartialOrd特征没法说明自身是否是全序,就不能完全满足堆的需求。
所以,最好就是直接实现cmp,然后用cmp推导出partial_cmp。
同理,自定义排序,虽然源码只会调用cmp而不会调用partial_cmp,但最好就是直接实现cmp,然后用cmp推导出partial_cmp。
(4)偏序集的等价自反子集
例如,我要用浮点数作为堆的类型,而且我知道肯定不会有NAN的出现,那么可以这么写:
#[derive(PartialEq,PartialOrd)]
struct FloatWithOrd{
x:f32, // do not let x be NAN
}
impl Eq for FloatWithOrd{
}
impl Ord for FloatWithOrd {
#[inline]
fn cmp(&self,other:&FloatWithOrd)->Ordering{
if(self.x < other.x){
return Ordering::Less;
}
if(self.x > other.x){
return Ordering::Greater;
}
return Ordering::Equal;
}
}
fn main() {
let mut heap = BinaryHeap::new();
heap.push(FloatWithOrd{x:1.5});
heap.push(FloatWithOrd{x:3.0});
assert_eq!(heap.peek().unwrap().x, 3.0);
println!("end");
}