第一阶段:RUST语法练习
variables6.rs
关于const:
Rust的常量必须加类型。那为什么常量不推导呢? 这是因为故意这样设计的,指定类型就不会因为自动推导类型出问题。
static, const, let 声明变量有什么区别? - Rust语言中文社区
1 | // variables6.rs |
primitive_types3.rs
数组(array)的三要素:
- 长度固定
- 元素必须有相同的类型
- 依次线性排列
数组必须要初始化,以下这种写法会报错:
1 | fn main() { |
改为这样就编译通过:
1 | fn main() { |
vecs2.rs
iter_mut():
iter_mut() 创建一个可变引用迭代器。当你想要修改集合中的元素时,应使用 iter_mut()。iter_mut() 返回的迭代器将生成集合中每个元素的可变引用。
1 | let mut v = vec![1, 2, 3]; |
move_semantics2.rs
1 | // move_semantics2.rs |
方法1:
将vec0的内容clone一份传进函数,然后返回值的所有权交给vec1,此时vec1=[22, 44, 66],vec0=[]
然后再把vec0的内容clone一份传进函数,然后返回值的所有权交给vec0,此时vec1=[22, 44, 66],vec0=[22, 44, 66]
这个时候不管是vec0还是vec1都拥有一片自己的堆上的空间,二者互不相关,因此vec1.push(88)只会改变vec1的值,且vec0的值也还存在
1 | fn main() { |
方法2:
首先,创建了vec0的一个可变引用&mut vec0,将这个可变引用传入函数,函数接受一个可变引用类型,然后对其进行操作,也就是操作了vec0指向的那片堆,因此在函数内部,vec0就已经变成了[22, 44, 66]
然后最后返回vec.to_vec(),相当于又创建了一个新的vec,作为vec1绑定的值,因此vec1和vec0又变成了互不相关的
1 | fn main() { |
move_semantics4.rs
这个的意思就是函数不再接收参数,而是直接在里面新创建一个包含[22, 44, 66]的vector返回
1 | // move_semantics4.rs |
structs3.rs
结构体,有几点值得注意:
- 初始化实例时,每个字段都需要进行初始化
- 初始化时的字段顺序不需要和结构体定义时的顺序一致
需要注意的是,必须要将结构体实例声明为可变的,才能修改其中的字段,Rust 不支持将某个结构体某个字段标记为可变。
结构体方法:
Unlike functions, methods are defined within the context of a struct,and their first parameter is always
self
, which represents the instance of the struct the method is being called on.we still need to use the
&
in front of theself
shorthand to indicate that this method borrows theSelf
instance, just as we did inrectangle: &Rectangle
. Methods can take ownership ofself
, borrowself
immutably, as we’ve done here, or borrowself
mutably, just as they can any other parameter.方法参数里面不止&self:
package.get_fees(cents_per_gram)
比如这个,get_fees是结构体的方法,它在结构体里面是这样定义的:
1
2
3
4fn get_fees(&self, cents_per_gram: i32) -> i32 {
// Something goes here...
self.weight_in_grams*cents_per_gram
}也就是说,第二个参数跟在&self后面就好了,在外部调用结构体时只需要传入那个另外的参数
例子:
1 | // structs3.rs |
enums2.rs
更为复杂的枚举:
Move:包含了一个匿名结构体
Echo:包含了一个String
ChangeColor:包含了三个整数
Quit:没有关联任何数据
1 | enum Message { |
enums3.rs
模式匹配和模式绑定
1 | // enums3.rs |
strings3.rs
字符串的字面量是切片
一般写字符串可以这样写:
let s = "Hello, world!";
实际上,
s
的类型是&str
,因此你也可以这样声明:let s: &str = "Hello, world!";
String转&str:取引用即可
1
2
3
4
5
6
7
8
9
10fn main() {
let s = String::from("hello,world!");
say_hello(&s);
say_hello(&s[..]);
say_hello(s.as_str());
}
fn say_hello(s: &str) {
println!("{}",s);
}&str转String:
"hello,world".to_string()
或者String::from("hello,world")
String的操作(必须把String声明为mut)
由于
String
是可变字符串,因此只有String
这种字符串可以被操作更改push()
,在末尾追加字符;push_str()
,在末尾追加字符串这两个方法都是在原有的字符串上追加,并不会返回新的字符串,即返回值是()
insert()
方法插入单个字符,insert_str()
方法插入字符串字面量也是在原有的字符串上面操作,没有返回值
replace
该方法可适用于String
和&str
类型该方法是返回一个新的字符串,而不是操作原来的字符串
1 | // strings3.rs |
hashmaps2.rs
问题1:
关于hashmap的更新实例中:为什么修改count的值就可以修改hashmap的键值呢?
1 | use std::collections::HashMap; |
解答:
map.entry(word)
返回了一个 Entry
枚举类型的值,该枚举有两个变体:Occupied
和 Vacant
。Entry
枚举表示 HashMap
中某个键对应的条目。
当调用 or_insert
方法时,如果 word
对应的条目已经存在,则 or_insert
方法会返回该条目的值的可变引用(Occupied
变体),如果该条目不存在,则会在 map
中插入一个新的键值对,然后返回新插入的值的可变引用(Vacant
变体)
问题2:
这道题里面有这样一个语句:
1 | **let mut basket = get_fruit_basket(); |
其中,basket是这样来的:
1 | fn get_fruit_basket() -> HashMap<Fruit, u32> { |
所以,为什么basket已经是一个hashmap了,还要用*解引用呢?
解答:
*解引用解的不是basket,而是basket.get(&Fruit::Apple),因为get会返回一个指向该条目键值的引用
跟这段代码一个道理:
1 | let mut scores = HashMap::new(); |
or_insert()会返回一个指向该条目键值的引用,因此也需要把它解引用来跟5比较
quiz2.rs
这道题主要注意Append这个命令:迭代器是向量中每个元素的引用(想想这是肯定的,不然在for循环里面所有权都丢失了的话有点太危险),而这种引用默认就是不可变引用,那么使用String类型的push_str()自然就是要报错的,因为这个方法是改变原字符串而不是返回一个新的字符串。
1 | // quiz2.rs |
options2.rs
主要是if let语句和while let语句的使用
首先,Option实际上是一种枚举类型,包含两个枚举成员:
1 | enum Option<T> { |
所以下面代码中的if let Some(word) = optional_target {}实际上就是将optional_target这个枚举类型的值结构到word上,然后进行一个操作
while let语句也是一个道理,由于optional_integers的元素都是Some类型的变量,因此首先要把optional_integers的值解构到Some(integer)上,然后再进行操作,因此出现一个Some包裹一个Some
1 | // options2.rs |
关于if let的详细讲解
if 和 if let 表达式 - Rust 参考手册 中文版
options3.rs
ref和&:
1 | let c = 'Q'; |
1 | // options3.rs |
errors2.rs
关于errors:
关于parse:
parse返回的是一个Result枚举类型,成功的话将会是Result::Ok(),失败是话是Result::Err,因此想获取parse成功时返回的正确值,需要将返回值unwrap()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22fn main() {
let astr = "7";
let n = astr.parse::<i32>().unwrap();
println!("{:?}", n);
let n = astr.parse::<i64>().unwrap();
println!("{:?}", n);
let n = astr.parse::<u32>().unwrap();
println!("{:?}", n);
let n = astr.parse::<u64>().unwrap();
println!("{:?}", n);
let astr = "7.42";
let n: f32 = astr.parse().unwrap();
println!("{:?}", n);
let n: f64 = astr.parse().unwrap();
println!("{:?}", n);
}认识到Result这个枚举类型:
[Result](https://rustwiki.org/zh-CN/std/result/enum.Result.html)
是[Option](https://rustwiki.org/zh-CN/std/option/enum.Option.html)
类型的更丰富的版本,描述的是可能的错误而不是可能的不存在。
而做题的时候这个例子,返回的错误类型是std::num::ParseIntError
1 | // errors2.rs |
还可以用?:
1 | pub fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> { |
errors3.rs
在函数里面使用?:
因为?的逻辑是这样的:
如果返回的不是Err,就正常运行并且将Result枚举类型的值结构给cost
但如果返回的是Err,函数就会立刻终止,然后返回一个Result::Err类型的值,因此必须给函数设置返回值
1 | // errors3.rs |
但是在main函数里面返回一个Result::Ok(String::from("implement success!"))
是不合法的,因为main
函数通常有两种有效的返回类型:()
(表示成功)和Result<(), E>
(表示成功或出现错误)。main
函数的返回类型必须实现Termination
trait,该trait规定了程序的终止行为
errors4.rs
可以像这样用匹配:
1 | fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> { |
但感觉这里也可以直接if判断:
1 | if value > 0 { |
errors6.rs
转换错误类型:将标准库定义的错误类型转换为自定义的ParsePosNonzeroError
1 | // errors6.rs |
关于map_err:
- Maps a Result<T, E> to Result<T, F> by applying a function to a contained Err value, leaving an Ok value untouched.
- 也就是不触发错误返回,而是等待下一步对这个错误的处理
代码:
1 | // 处理parse()出错的情况 |
- 首先
s.parse()
尝试将s进行转换:- 如果出错的话,
map_err()
就会返回ParsePosNonzeroError::from_parseint
这个自定义的错误类型然后直接返回,而不是ParseIntError
- 如果没出错,
map_err()
就会返回一个Result::Ok(value)作为s转换后的值——因此后面一定要加?,不然出错的情况确实可以正常返回,但是没出错时Result类型的值是无法与i64类型的x绑定的,而?可以将正常返回的值直接unwrap
- 如果出错的话,
- 然后x被成功绑定一个i64类型的整数,就需要判断x是否满足非负数,因此创建一个
PositiveNonzeroInteger
类型的结构体,x的值作为结构体的值,然后调用结构体方法new()- 如果new成功返回Ok(PositiveNonzeroInteger(x as u64)),那么map_err()就返回这个Result::Ok类型的值
- 如果new出错了返回了一个Result::Err类型的值,那么map_err()就返回
ParsePosNonzeroError::from_creation
这个自定义的错误类型然后直接返回,而不是CreationError
generics2.rs
使用泛型
1 | // generics2.rs |
traits5.rs
特征的多重约束
1 | // traits5.rs |
quiz3.rs
关于格式化字符串format!:并不是所有的泛型都可以用这个函数,必须要有Display特征的泛型才可以,因此使用T: Display这个特征约束
pub fn notify<T: Summary>(item1: &T, item2: &T) {}
像这段代码的意思就是:约束T必须具有特征Summary,且item1和item2都是T类型的引用
1 | // quiz3.rs |
lifetimes3.rs
关于结构体的生命周期:
不仅仅函数具有生命周期,结构体其实也有这个概念,只不过我们之前对结构体的使用都停留在非引用类型字段上。细心的同学应该能回想起来,之前为什么不在结构体中使用字符串字面量或者字符串切片,而是统一使用 String
类型?原因很简单,后者在结构体初始化时,只要转移所有权即可,而前者,抱歉,它们是引用,它们不能为所欲为。
既然之前已经理解了生命周期,那么意味着在结构体中使用引用也变得可能:只要为结构体中的每一个引用标注上生命周期即可:
1 | struct ImportantExcerpt<'a> { |
结构体 ImportantExcerpt
所引用的字符串 str
生命周期需要大于等于该结构体的生命周期。
iterators2.rs
迭代器:
迭代器是会被消耗的:
当使用了next(),迭代器就会被消耗,相当于取出了那一个元素,那个元素其实已经不在迭代器里面了,因此最开始我直接将first转换为大写后直接返回了迭代器剩余的元素,这会导致返回的字符串没有预期的第一个字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// Step 1.
// Complete the `capitalize_first` function.
// "hello" -> "Hello"
pub fn capitalize_first(input: &str) -> String {
let mut c = input.chars();
match c.next() {
None => String::new(),
Some(first) => {
let mut first_upp: String = first.to_uppercase().collect();
let chars_str: String = c.collect();
first_upp.push_str(&chars_str);
first_upp
}
}
}
iterators4.rs
用迭代器实现递归:使用消费者适配器的方法
1 | pub fn factorial(num: u64) -> u64 { |
在 Rust 中,(1..=num)
是一个范围(Range)表达式,表示从1到num
(包括num
本身)的一个范围。这个范围实际上是一个迭代器,它可以生成从1到num
的所有数字序列。
在这种情况下,我们可以通过 (1..=num)
创建一个包含从1到num
的数字序列的迭代器。然后,我们调用迭代器的 product()
方法,这个方法将迭代器中的所有元素相乘,得到它们的乘积作为结果。
因此,(1..=num).product()
这个语句的意思是:先生成从1到num
的数字序列迭代器,然后计算这个序列中所有数字的乘积,最终得到阶乘的结果。
iterators5.rs
关于闭包和迭代器方法的使用
不用循环,找出map里面有多少个键值为value的元素
1 | fn count_iterator(map: &HashMap<String, Progress>, value: Progress) -> usize { |
.values()
:使用了HashMap的value方法,获取了一个包含所有键值的迭代器。pub fn [values](https://rustwiki.org/zh-CN/std/collections/struct.HashMap.html#method.values)(&self) -> [Values](https://rustwiki.org/zh-CN/std/collections/hash_map/struct.Values.html)<'_, K, V>
一个以任意顺序访问所有值的迭代器。 迭代器元素类型为&'a V
,即对键值的引用.filter()
:创建一个迭代器,该迭代器使用闭包确定是否应产生元素。给定一个元素,闭包必须返回true
或false
。返回的迭代器将仅生成闭包为其返回 true 的元素。.count()
:消耗迭代器,计算迭代次数并返回它。此方法将反复调用[next](https://rustwiki.org/zh-CN/std/iter/trait.Iterator.html#tymethod.next)
,直到遇到[None](https://rustwiki.org/zh-CN/std/option/enum.Option.html#variant.None),
并返回它看到[Some](https://rustwiki.org/zh-CN/std/option/enum.Option.html#variant.Some)
的次数。 请注意,即使迭代器没有任何元素,也必须至少调用一次[next](https://rustwiki.org/zh-CN/std/iter/trait.Iterator.html#tymethod.next)
。
返回一个所有哈希表里面键值为指定状态的数量和:
1 | fn count_collection_iterator(collection: &[HashMap<String, Progress>], value: Progress) -> usize { |
.map()
:Iterator特征的函数,获取一个闭包并创建一个迭代器,该迭代器在每个元素上调用该闭包。- 首先创建collection的迭代器,再使用map使得迭代器上的每个元素都调用count_iterator(),对于每个哈希表都计算键值为progress的个数
.sum()
:将迭代器里面的每个元素相加,所以这么写也是对的:
1 | let iter = collection.iter().map(|map| count_iterator(map, value)); |
box1.rs——智能指针
Box堆对象分配 - Rust语言圣经(Rust Course)
1 | // 使用Box来定义Cons,将List存到堆上,那么List的大小就固定了 |
threads1.rs
1 | fn main() { |
线程返回值是如何得到的?
在
handle.join().unwrap()
中,join()
方法会等待线程执行完成并获取线程的返回值,即每个线程的执行时间(以毫秒为单位),然后通过unwrap()
方法将其取出并存储在results
向量中。线程编号和时间是如何输出的?
results.into_iter().enumerate()
:into_iter()
方法将results
向量转换为一个拥有所有权的迭代器,enumerate()
方法对迭代器进行索引迭代,返回一个元组(index, value)
,其中index
表示元素在迭代器中的索引,value
表示元素的值。
threads3.rs——多个发送者
为什么这里需要克隆发送方?
- 因为thread使用了闭包关键字move,这会使得在创建第一个线程时,tx的所有权已经被移到了第一个线程里面,第二个线程还想用tx发送信息自然是做不到的
- 因此克隆一个tx,第二个线程就可以使用了
1 | fn send_tx(q: Queue, tx: mpsc::Sender<u32>) -> () { |
macros3.rs
- 使用macro_rules!来定义宏
- 宏的定义必须在调用之前
- 通过在
my_macro
宏定义前加上#[macro_export]
属性,使得宏可以在模块外部使用
1 | pub mod macros { |
macros4.rs
模式匹配:
$()
中包含的是模式$x:expr
,该模式中的expr
表示会匹配任何 Rust 表达式,并给予该模式一个名称$x
- 因此
$x
模式可以跟整数1
进行匹配,也可以跟字符串 “hello” 进行匹配:vec!["hello", "world"]
$()
之后的逗号,意味着1
和2
之间可以使用逗号进行分割,也意味着3
既可以没有逗号,也可以有逗号:vec![1, 2, 3,]
*
说明之前的模式可以出现零次也可以任意次,这里出现了三次
匹配一次:
1 |
|
匹配多次:
1 |
|
tests5.rs——裸指针和unsafe
在裸指针
*const T
中,这里的*
只是类型名称的一部分,并没有解引用的含义下面的代码基于值的引用同时创建了可变和不可变的裸指针,创建裸指针是安全的行为,而解引用裸指针才是不安全的行为 :
1
2
3
4let mut num = 5;
let r1 = &num as *const i32; // 不可变裸指针
let r2 = &mut num as *mut i32; // 可变裸指针
algorithm1——合并链表
1 | pub fn merge(list_a:LinkedList<T>,list_b:LinkedList<T>) -> Self |
algorithm2——链表反转
因为用到了clone,因此必须限定T是具有Clone特征的泛型
方法一
- 使用
std::mem::replace(self, reversed_list);
交换新链表和self的所有权
- 使用
方法二
- 直接将新链表的所有权交给self
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23impl<T: Clone> LinkedList<T> {
pub fn reverse(&mut self){
let mut reversed_list = LinkedList::new();
// 获取链表的末尾节点
let mut current_node = self.end;
while current_node.is_some() {
// 获取当前节点值的引用
let value = current_node.map(|ptr| unsafe{ &(*ptr.as_ptr()).val });
match value {
Some(x) => {
// 新链表加入x
reversed_list.add(x.clone());
// 指向上一个节点
current_node = unsafe{ (*current_node.unwrap().as_ptr()).prev };
}
None => break
}
}
// 交换新链表和原链表的所有权
// std::mem::replace(self, reversed_list);
*self = reversed_list;
}
}其中while循环那段可以这样改:
1
2
3
4
5while let Some(node_ptr) = current_node {
let value = unsafe { &(*node_ptr.as_ptr()).val }; // 因为node_ptr肯定是Some类型的,其值一定存在
reversed_list.add(value.clone());
current_node = unsafe { (*node_ptr.as_ptr()).prev };
}
algorithm4——二叉查找树
因为要递归实现插入和查找,所以应该将search和insert实现为TreeNode的方法,所以TreeNode方法的实现应该要写在最前面
二叉树节点的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35// 此时self是二叉树里面的一个节点
fn insert(&mut self, value: T) {
match value.cmp(&self.value) {
// 要插入的节点小于当前节点
Ordering::Less => {
// 应该把它往左边递归
match &mut self.left {
Some(left) => {
// 如果左指针有值,就继续递归
// 左指针是一个Box
(*left).insert(value);
}
_ => {
// 如果左指针指向None,就让它指向新建节点
self.left = Some(Box::new(TreeNode::new(value)));
}
}
}
Ordering::Greater => {
// 应该把它往右边递归
match &mut self.right {
Some(right) => {
// 如果右指针有值,就继续递归
// 右指针是一个Box
(*right).insert(value);
}
_ => {
// 如果右指针指向None,就让它指向新建节点
self.right = Some(Box::new(TreeNode::new(value)));
}
}
}
_ => {} // 相等时不插入新节点
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19fn search(&self, value: T) -> bool {
match value.cmp(&self.value) {
Ordering::Less => {
// 说明应该往当前节点的左边找
match &self.left {
Some(left) => { return left.search(value); }
_ => { return false; }
}
}
Ordering::Greater => {
// 说明应该往当前节点的右边找
match &self.right {
Some(right) => { return right.search(value); }
_ => { return false; }
}
}
_ => { return true; }
}
}二叉树的方法
1
2
3
4
5
6
7
8
9
10
11
12// Insert a value into the BST
fn insert(&mut self, value: T) {
// 直接使用节点的插入方法
match &mut self.root {
Some(root_node) => {
root_node.insert(value);
}
_ => {
self.root = Some(Box::new(TreeNode::new(value)));
}
}
}1
2
3
4
5
6
7// Search for a value in the BST
fn search(&self, value: T) -> bool {
match &self.root {
Some(root_node) => { root_node.search(value) }
_ => { false }
}
}algorithm7——用Vec实现栈
1
2
3
4
5
6
7// 坑:size记得-=1
fn pop(&mut self) -> Option<T> {
if self.size > 0 {
self.size -= 1;
}
self.data.pop()
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26fn bracket_match(bracket: &str) -> bool
{
let mut stack = Stack::new();
// 遍历字符串里面的字符
for chr in bracket.chars() {
// 扫描到左括号就入栈
if chr == '(' || chr == '{' || chr == '[' {
stack.push(chr);
}
if chr == ')' || chr == '}' || chr == ']' {
if stack.is_empty() {
return false;
}
if let Some(stack_top) = stack.pop() {
if chr == ')' {
if stack_top != '(' { return false; }
} else if chr == '}' {
if stack_top != '{' { return false; }
} else if chr == ']' {
if stack_top != '[' { return false; }
}
}
}
}
stack.is_empty()
}algorithm8——两个队列实现栈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 出队,出第一个元素
pub fn dequeue(&mut self) -> Result<T, &str> {
if !self.elements.is_empty() {
Ok(self.elements.remove(0usize))
} else {
Err("Stack is empty") // 坑:记得把原来的“Queue”改成“Stack”
}
}
// 看队头的元素的值
pub fn peek(&self) -> Result<&T, &str> {
match self.elements.first() {
Some(value) => Ok(value),
None => Err("Stack is empty"),
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18pub fn push(&mut self, elem: T) {
self.q1.enqueue(elem); // 入队列q1
}
pub fn pop(&mut self) -> Result<T, &str> {
// 将q1里面的除最后一个元素依次出栈存到q2
for n in 1..self.q1.size() {
if let Ok(value) = self.q1.dequeue() {
self.q2.enqueue(value);
}
}
std::mem::swap(&mut self.q1, &mut self.q2); // 交换q1、q2所有权
self.q2.dequeue() // q2负责出队
}
pub fn is_empty(&self) -> bool {
self.q1.is_empty() && self.q2.is_empty()
}algorithm9——堆排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66impl<T> Heap<T>
where
T: Default + std::cmp::PartialOrd
{
pub fn new(comparator: fn(&T, &T) -> bool) -> Self {
Self {
count: 0,
items: vec![T::default()], // 从下标为1的地方开始存堆
comparator,
}
}
pub fn len(&self) -> usize {
self.count
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn add(&mut self, value: T) {
self.count += 1; // index从1开始
self.items.push(value); // 先在堆的最后添加一个节点
let mut index = self.count;
while index > 1 {
let parent_idx = self.parent_idx(index); // 获取最后一个元素的父元素
if (self.comparator)(&self.items[index], &self.items[parent_idx]) {
self.items.swap(index, parent_idx); // 将它和父元素交换
index = parent_idx; // 发生交换后index移动到父元素的位置
} else {
break;
}
}
}
fn parent_idx(&self, idx: usize) -> usize {
idx / 2
}
// 判断idx是否有孩子
fn children_present(&self, idx: usize) -> bool {
self.left_child_idx(idx) <= self.count
}
fn left_child_idx(&self, idx: usize) -> usize {
idx * 2
}
fn right_child_idx(&self, idx: usize) -> usize {
self.left_child_idx(idx) + 1
}
// 按堆规定的顺序返回两个孩子中应该在前面的一个的索引值
fn smallest_child_idx(&self, idx: usize) -> usize {
let left = self.left_child_idx(idx);
let right = self.right_child_idx(idx);
if right > self.count {
left
} else if (self.comparator)(&self.items[left], &self.items[right]) {
left
} else {
right
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23fn next(&mut self) -> Option<T> {
if self.count == 0 {
return None;
}
// 相当于堆排序
let ret = self.items.swap_remove(1); // 将堆顶元素输出,堆底元素升上来
self.count -= 1;
// 再次调整堆为大根堆或小根堆
let mut index = 1;
// 当当前元素有子元素时
while self.children_present(index) {
let child_index = self.smallest_child_idx(index);
if (self.comparator)(&self.items[child_index], &self.items[index]) {
self.items.swap(child_index, index);
index = child_index;
}
else {
break; // 如果不break会进入死循环
}
}
Some(ret)
}algorithm10——图
用到了
get_mut()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44// 无向图结构的邻接表,用HashMap来存储,键是String,键值是一个向量,其中i32可能表示边长
pub struct UndirectedGraph {
adjacency_table: HashMap<String, Vec<(String, i32)>>,
}
impl Graph for UndirectedGraph {
fn new() -> UndirectedGraph {
UndirectedGraph {
adjacency_table: HashMap::new(),
}
}
// 获取邻接表的可变引用
fn adjacency_table_mutable(&mut self) -> &mut HashMap<String, Vec<(String, i32)>> {
&mut self.adjacency_table
}
// 获取邻接表的引用
fn adjacency_table(&self) -> &HashMap<String, Vec<(String, i32)>> {
&self.adjacency_table
}
// 添加边
fn add_edge(&mut self, edge: (&str, &str, i32)) {
// 要在三元组的第一个str和第二个str之间加边
let (vertice1, vertice2, weight) = edge;
// 如果第一个点查询得到
if let Some(adj1) = self.adjacency_table_mutable().get_mut(&String::from(vertice1)) {
adj1.push((String::from(vertice2), weight));
} else {
self.add_node(vertice1);
if let Some(new_adj1) = self.adjacency_table_mutable().get_mut(vertice1) {
new_adj1.push((String::from(vertice2), weight));
}
}
// 如果第二个点查询得到
if let Some(adj2) = self.adjacency_table_mutable().get_mut(vertice2) {
adj2.push((String::from(vertice1), weight));
} else {
self.add_node(vertice2);
if let Some(new_adj2) = self.adjacency_table_mutable().get_mut(&String::from(vertice2)) {
new_adj2.push((String::from(vertice1), weight));
}
}
}
}1
2
3
4
5
6
7
8
9
10
11
12// 添加成功返回true,添加失败返回false
fn add_node(&mut self, node: &str) -> bool {
if self.contains(node) {
return false;
}
else {
self.adjacency_table_mutable().insert(node.to_string(), Vec::new());
return true;
}
}
// 在结构体方法中实现了
fn add_edge(&mut self, edge: (&str, &str, i32));