[Rust]6更多集合和更多错误处理

本章梗概

  • Other collections—more complex and interesting ones this time
  • The question mark operator—just type ? to handle errors
  •  When panic and unwrap are good

集合

使用其它更多的集合类型需引用std::collections

use std::collection

具体使用标准库那些模块可以参考 http://mng.bz/27yd

HashMap 哈希表

pub struct HashMap<K, V, S = RandomState, A: Allocator = Global> { /* private fields */ }

HashMap 是散列表是一种利用哈希函数实现 key → value 快速映射 的数据结构,平均查询复杂度为 O(1)

HashMap的存储是无需的

use std::collections::HashMap;

struct City {
    name : String,
    population : HashMap<i32,i32>,
}

fn main() {
    let mut taillin = City {
        name:   "Tallinn".to_string(),
        population: HashMap::new(), 
    };

    taillin.population.insert(2020, 437_619);
    taillin.population.insert(1372, 3_250);
    taillin.population.insert(1851, 24_000);

    for (year,population) in taillin.population  {
        println!("In {year},Tallinn had a population of {population},");
        
    }
}

HashMap组成主要是由Key和Value组成,由一个Key对应一个Value

.get()函数 用于通过Value的到Key,如果得到返回Some(value)没有得到则返回None

演示代码
use std::collections::HashMap;
fn main() {
    let canadian_cities = vec!["Calgary", "Vancouver", "Gimli"];
    let german_cities = vec!["Karlsruhe", "Bad Doberan", "Bielefeld"];
    let mut city_hashmap = HashMap::new();
    for city in canadian_cities {
        city_hashmap.insert(city, "Canada");
    }
    for city in german_cities {
        city_hashmap.insert(city, "Germany");
    }
    println!("{:?}", city_hashmap["Bielefeld"]);
    println!("{:?}", city_hashmap.get("Bielefeld"));
    println!("{:?}", city_hashmap.get("Bielefeldd"));
}

/*
"Germany"
Some("Germany")
None
*/

.insert()函数 用于插入新的Value到已有的Key中,如果成功覆盖后会返回被替换的旧值以Option()形式返回

演示代码
use std::collections::HashMap;
fn main() {
    let mut book_hashmap = HashMap::new();
    let mut old_hashmap_values = Vec::new();
    let hashmap_entries = [
        (1, "L'Allemagne Moderne"),
        (1, "Le Petit Prince"),
        (1, "섀도우 오브 유어 스마일"),
        (1, "Eye of the World"),
    ];

    for (key, value) in hashmap_entries {
        if let Some(old_value) = book_hashmap.insert(key, value) {
        println!("Overwriting {old_value} with {value}!");
        old_hashmap_values.push(old_value);
        }
    }
    println!("All old values:{old_hashmap_values:?}");
}
/*
Overwriting L'Allemagne Moderne with Le Petit Prince!
Overwriting Le Petit Prince with 섀도우 오브 유어 스마일!
Overwriting 섀도우 오브 유어 스마일 with Eye of the World!
All old values: ["L'Allemagne Moderne", "Le Petit Prince", "섀도우 오브 유어 스
마일"]
*/

.entry()函数 https://mng.bz/1JXV 用来在一次查找中同时处理“存在 / 不存在”两种情况,避免重复查找,提高性能并简化代码。

enum Entry<K, V> {
    Occupied(OccupiedEntry<K, V>),
    Vacant(VacantEntry<K, V>),
}
  • .or_insert() 确保条目中存在值,如果为空则插入默认值,并返回条目中值的可变引用。
  • .or_insert_with() 确保条目中存在值,如果为空则插入默认函数的结果,并返回条目中值的可变引用。
  • .or_insert_with_key() 确保条目中存在值,如果条目为空,则插入默认函数的结果。此方法允许通过向默认函数提供在 .entry(key) 方法调用期间移动的键的引用,来生成键派生的值进行插入。
演示代码1
use std::collections::HashMap;
fn main() {
    let book_collection = vec![
        "L'Allemagne Moderne",
        "Le Petit Prince",
        "Eye of the World",
        "Eye of the World",
    ];
    let mut book_hashmap = HashMap::new();

    for book in book_collection {
        let return_value = book_hashmap.entry(book).or_insert(0);
        *return_value += 1;
    }   

    for (book, number) in book_hashmap {
        println!("{book}, {number}");
    }
}
演示代码2
use std::collections::HashMap;
fn main() {
    let data = vec![
        ("male", 9),
        ("female", 5),
        ("male", 0),
        ("female", 6),
        ("female", 5),
        ("male", 10),
    ];
    let mut survey_hash = HashMap::new();
    for item in data {
        survey_hash.entry(item.0).or_insert(Vec::new()).push(item.1);
    }
    for (male_or_female, numbers) in survey_hash {
        println!("{male_or_female}: {numbers:?}");
    }
}

Hashset

HashSet<T>是以哈希图实现的无序不允许重复的哈希集合,只关注key的键而不关心value值为()。只存“唯一元素”,不存在重复,并且查找十分快(平均 O(1))

演示代码
use std::collections::HashSet;
fn main() {
    let many_numbers = vec![
        37, 3, 25, 11, 27, 3, 37, 21, 36, 19, 37, 30, 48, 28, 16, 33, 2,
        10, 1, 12, 38, 35, 30, 21,
        20, 38, 16, 48, 39, 31, 41, 32, 50, 7, 15, 1, 20, 3, 33, 12, 1, 11,
        34, 38, 49, 1, 27, 9,
        46, 33,
    ];
    println!("How many numbers in the Vec? {}", many_numbers.len());
    let mut number_hashset = HashSet::new();
    for number in many_numbers {
        number_hashset.insert(number);
    }

    let hashset_length = number_hashset.len();
    println!(
        "There are {hashset_length} unique numbers, so we are missing {}.",
        50 - hashset_length
    );
    println!("It does not contain: ");
    for number in 0..=50 {
        if number_hashset.get(&number).is_none() {
            print!("{number} ");
        }
    }
}

/*
How many numbers in the Vec? 50
There are 31 unique numbers, so we are missing 19.
It does not contain: 
0 4 5 6 8 13 14 17 18 22 23 24 26 29 40 42 43 44 45 47
*/

BTreeMap B树映射

use std::collections::BTreeMap;

struct City {
name: String,
population: BTreeMap<i32, i32>,
}

fn main() {
    let mut tallinn = City {
        name: "Tallinn".to_string(),
        population: BTreeMap::new(),
    };
    tallinn.population.insert(2020, 437_619);
    tallinn.population.insert(1372, 3_250);
    tallinn.population.insert(1851, 24_000);
    for (year, population) in tallinn.population {
        println!("In {year}, Tallinn had a population of {population}.");
    }
}

BTreeSet

BTreeSet<T>是基于BTressMap的有序不允许重复的集合,自动排序默认从小到大 基于Ord trait 时间复杂度O(log n) 支持范围查询,和HashSet很相似。

演示代码
use std::collections::BTreeSet;
fn main() {
    let many_numbers = vec![37, 3, 25, 11, 27, 3, 37, 21, 36, 19, 37, 30, 48,
        28, 16, 33, 2, 10, 1, 12, 38, 35, 30, 21, 20, 38, 16, 48, 39, 31, 41,
        32, 50, 7, 15, 1, 20, 3, 33, 12, 1, 11, 34, 38, 49, 1, 27, 9, 46, 33];
    let mut current_number = i32::MIN;
    let mut number_set = BTreeSet::new();
    for number in many_numbers {
        number_set.insert(number);
    }
    for number in number_set {
        if number < current_number {
            println!("This will never happen");
        }
        current_number = number;
    }
}
/*
不会打印任何东西,因为每一个数字都比前一个数字大
*/

BinaryHeap 二叉堆

从二叉堆的结构说起,它是一棵二叉树,并且是完全二叉树,每个结点中存有一个元素(或者说,有个权值).

堆性质:父亲的权值不小于儿子的权值(大根堆).同样的,我们可以定义小根堆.本文以大根堆为例.

大根堆: 父结点的值 大于或等于 其子结点的值
小根堆: 父结点的值 小于或等于 其子结点的值

大根堆 示例代码
use std::collections::BinaryHeap;
fn main() {
    let many_numbers = vec![0, 5, 10, 15, 20, 25, 30];
    let mut heap = BinaryHeap::new();
    for num in many_numbers {
        heap.push(num);
    }
    println!("First item is largest, others are out of order: {heap:?}");
    while let Some(num) = heap.pop() {
        println!("Popped off {num}. Remaining numbers are: {heap:?}");
    }
}

VecDeque 双向队列

使用可增长的环形缓冲区实现的双端队列 

  • 您需要一个支持在序列两端高效插入的 Vec
  • 您需要一个队列。
  • 您需要一个双端队列 (deque)。 
在上情况使用

?运算符

?是一个更加简短的方式处理Result,相比于match和if let。

  • 如果结果是Ok,返回Result中的内容
  • 如果结果是Err,则直接略过
也可以处理option但大多处理Result
演示代码
use std::num::ParseIntError;
    fn parse_and_log_str(input: &str) -> Result<i32, ParseIntError> {
        let parsed_number = input.parse::<i32>()?;
        println!("Number parsed successfully into {parsed_number}");
        Ok(parsed_number)
}
    fn main() {
    let str_vec = vec!["Seven", "8", "9.0", "nice", "6060"];
    for item in str_vec {
        let parsed = parse_and_log_str(item);
        println!("Result: {parsed:?}");
    }
}

既 ? 可以提前返回函数那么在mian()函数中也可是带Err()返回结束而不报错parse

演示代码
use std::num::ParseIntError;
fn main() -> Result<(), ParseIntError> {
    for item in vec!["89", "8", "9.0", "eleven", "6060"] {
        let parsed = item.parse::<u32>()?;
        println!("{parsed}");
    }
    Ok(())
}
  • ? 是早返回机制
  • 返回的 Err 是 ParseIntError 结构体,包含失败信息
  • 连续调用只要错误类型一致,整个链条都能用 ? 简洁表达
演示代码
use std::num::ParseIntError;
fn parse_str(input: &str) -> Result<i32, ParseIntError> {
let parsed_number = input
    .parse::<u16>()?
    .to_string()
    .parse::<u32>()?
    .to_string()
    .parse::<i32>()?;
    println!("Number parsed successfully into {parsed_number}");

    Ok(parsed_number)
}
fn main() {
    let str_vec = vec!["Seven", "8", "9.0", "nice", "6060"];
    for item in str_vec {
    let parsed = parse_str(item);
    println!("{parsed:?}");
    }
}

何时使用panic和unwrap

Rust 语言提供了两个互补的系统,用于构建/表示、报告、传播、响应和丢弃错误。这些职责统称为“错误处理”。 panic! 和 Result 在它们各自错误处理系统中的主要接口方面是相似的;然而,这些接口附加给它们的错误的意义以及它们在其各自错误处理系统中所承担的职责有所不同

panic! 宏用于构建表示程序中已检测到错误的错误。使用 panic! 你提供描述错误的文本,然后语言会使用该文本构建错误,报告它,并为你传播它。

Result 另一方面用于封装其他类型,这些类型代表某种计算的成功结果 Ok(T) ,或表示该计算预期运行时失败模式的错误类型 Err(E)Result 与用户定义的类型一起使用,这些类型代表关联计算可能遇到的预期运行时失败模式。 Result 必须手动传播,通常借助 ? 操作符和 Try 特性,并且必须手动报告,通常借助 Error 特性。

std::result

panic

panic! 这是一种对错误条件的响应,这种错误通常在遇到它的上下文中是不可恢复的。它会立即终止当前线程,并在默认情况下打印错误信息及发生位置。panic! 常用于程序中检测到逻辑错误或 bug时,例如数组越界、除以零等。

可以携带信息返回并终止线程
fn main() {
    panic!("Time to panic!");
}

有三种函数类似panic! 可以用来测试

  • assert!(expression)如果括号内的表达式的值是 false 则 panic
  • assert_eq!(left, right)如果括号两边数值不等则panic
  • assert_ne!()如果括号两边数值相等则panic

unwrap

在错误处理中使用unwrap()得不到错误信息因此尝试使用expect()可以得到错误信息利于出问题时的识读

使用unwrap()
fn get_fourth(input: &Vec<i32>) -> i32 {
    let fourth = input.get(3).unwrap();
    *fourth
}
fn main() {
    let my_vec = vec![9, 0, 10, 8, 7];
    let fourth = get_fourth(&my_vec);
    println!("The fourth item is: {}", fourth);
}

/*
thread 'main' panicked at 'called Option::unwrap() on a None value', src\main.rs:7:18.
*/
使用expect()
fn get_fourth(input: &Vec<i32>) -> i32 {
    let fourth = input.get(3).expect("Input vector needs at least 4 items");
    *fourth
}
fn main() {
    let my_vec = vec![9, 0, 10, 8, 7];
    let fourth = get_fourth(&my_vec);
    println!("The fourth item is: {}", fourth);
}

/*
thread 'main' panicked at 'Input vector needs at least 4 items',
src\main.rs:7:18
*/

总结

  • 对于有键和值的数据,通常使用Hashmap。如果需要有序排序使用BTreemap
  • 如果只想验证某种东西是否存在,通常使用HashSet。如果需要有序集合使用BTreeSet
  • VecDequeVec慢,除非你需要同时在前面和后面操作数据。在这种情况下,VecDeque要快得多。
  • BTreeSet总是在前面具有最大的值。其余一切都没有分类。
  • 使用很方便,它会自动从返回的结果中提取OK值。如果值是Err,将提前退出函数并返回错误。
  • 使用ResultOption,您可以避免程序panic,但有时panic是有意义的。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注