Build a linked hash map in Rust
We're writing it end-to-end in one sitting, with the hope of ending up with a decent understanding of how hash map works, and how you'd make the interface idiomatic Rust. I have tried to make sure I introduce new concepts we come across, so it should be possible to follow whether you're a newcomer to the language or not.
影片注解
Data structure and API
Usually it is nicer tosepecify the bounds only on the places where you need them (e.g. methods) rather than on the data structure.
- Struct std::collections::HashMap
|
|
Hash and Eq
- Trait std::hash::Hash
When implementing both Hash
and Eq
, it is important that the following property holds:
|
|
In other words, if two keys are equal, their hashes must also be equal. HashMap
and HashSet
both rely on this behavior.
usize vs. u64
|
|
因为取模 %
运算后的数值不大于 buckets.len()
,并且 buckets.len()
的类型是 usize
,所以可以将取模运算的结果安全第转换成 usize
,当然进行取模运算时需要将 buckets.len()
转换成和 finish()
的返回值类型 u64
再进行运算。
swap_remove
对于普通的 vector 的 remove
方法来说,处理过程如下:
|
|
即需要被删除元素后面的元素依次进行移位,这样的时间复杂度很高 $O(n)$。但对于 swap_remove
来说,其处理过程如下:
|
|
先将被删除元素和最后一个元素交换位置,然后再丢弃最后一个元素 (此时该位置上为被删除元素),这样的时间复杂度仅为 $O(1)$
tail recursion
因为 Rust 编译器并没有针对尾递归的最优化,所以尽量不要使用尾递归的逻辑,使用循环改写比较好,这样可以将空间复杂度从 $O(n)$ 降到 $O(1)$。在 drop
方法的实现中特别明显,手动实现 drop
方法时,应尽量使用循环而不是递归逻辑。
tuple references
|
|
这两种表示方式是不完全相同的,对于第二种方式,隐含了一个前提: K, V
是在同一个 tuple 里面,即它们在内存的位置是相邻的,这种方式表示引用的是一个 tuple。而第一种仅表示两个引用组成了一个 tuple,而对于 K, V
这两个数据在内存的位置关系并无限制,K, V
本身是否组成 tuple 也不在乎。
borrow
- Trait std::borrow::Borrow
Types express that they can be borrowed as some type
T
by implementingBorrow<T>
, providing a reference to aT
in the trait’s borrow method. A type is free to borrow as several different types. If it wishes to mutably borrow as the type, allowing the underlying data to be modified, it can additionally implementBorrowMut<T>
.
In particular
Eq
,Ord
andHash
must be equivalent for borrowed and owned values:x.borrow() == y.borrow()
should give the same result asx == y
.
If generic code merely needs to work for all types that can provide a reference to related type
T
, it is often better to useAsRef<T>
as more types can safely implement it.
By additionally requiring
Q: Hash + Eq
, it signals the requirement thatK
andQ
have implementations of theHash
andEq
traits that produce identical results.
We can see how they’re kind of the same: they both deal with owned and borrowed versions of some type. However, they’re a bit different.
Choose Borrow
when you want to abstract over different kinds of borrowing, or when you’re building a data structure that treats owned and borrowed values in equivalent ways, such as hashing and comparison.
Choose AsRef
when you want to convert something to a reference directly, and you’re writing generic code.
entry
|
|
A view into a single entry in a map, which may either be vacant or occupied.
or_insert
和 or_insert_with
的可以从下面的例子一窥区别:
|
|
or_insert
会在调用前对参数进行计算,所以不管 x
是哪个枚举子类型,vec::new()
都会被调用,而 or_insert_with
的参数是一个闭包,仅当 x
是 Vacant
时才会对参数进行调用操作,即 vec::new()
操作。
|
|
reborrow
|
|
这个实作乍一看好像没有问题,但是注意 match
表达式让 iter_mut()
获得的可变引用的存活域为其接下来的 {}
内。但是需要注意的是,这个 iter_mut()
获得的可变引用是对该方法的 &mut self
进行 reborrow 得来的,依据 reborrow 的规则,在 reborrow 得到的可变引用的使用范围内,不能使用被 reborrow 的可变引用 (这是为了向编译器保证同一时刻只会存在一个可变引用)。但是我们看到 match
表达式的 None
分支里,使用了被 reborrow 的可变引用 self
,这违反了 reborrow 的规则,故而编译不通过。
正确实作如下,仅在 Some
和 None
分支才使用 reborrow,这样就不会违反 reborrow 的规则机制:
|
|
sorted list
可以给 hash map 的 linked 部分进行排序,这样查找的效能会比较高 (使用二分查找,时间复杂度由原先的 $O(n)$ 降低为 $O(log n)$),但是这样会降低插入的效能 (时间复杂度由原先的 $O(1)$ 提高至 $O(n)$)。所以需要根据应用场景进行 trade-off,如果是应用场景是查询操作比较多的,就将 linked 部分设置为有序。
Homework
- 为
HashMap
实现 Trait std::ops::Index,使得下面这条语句编译通过:
|
|
- 为
HashMap
实现 method and_modify,使得下面这条语句编译通过:
|
|
- 为
HashMap
实现 Trait std::convert::From,根据手册,只需要实现对数组类型[(K, V); N]
,使得下面的代码可以通过编译:
|
|
|
|
修正
bucket
方法,使得其对于空的HashMap
也可以正常工作在方法 from_iter 的实作中采用对
HashMap
进行预分配的策略,增强该方法的效能为
HashMap
实现&mut
的迭代器为
HashMap
实现 drain 方法为
HashMap
实现 remove_entry 方法为
HashMap
实现 get_mut 方法
Documentations
这里列举视频中一些概念相关的 documentation
学习的一手资料是官方文档,请务必自主学会阅读规格书之类的资料
Crate std
Struct std::collections::HashMap
Trait std::hash::Hasher
Trait std::hash::Hash
Trait std::borrow::Borrow
Trait std::borrow::BorrowMut
Function std::mem::replace
Struct std::vec::Vec
- method std::vec::Vec::with_capacity
- method std::vec::Vec::drain
- method std::vec::Vec::is_empty
- method std::vec::Vec::retain
- method std::vec::Vec::swap_remove
Trait std::iter::Iterator
- method std::iter::Iterator::find
- method std::iter::Iterator::map
- method std::iter::Iterator::flat_map
- method std::iter::Iterator::position
- method std::iter::Iterator::collect
- method std::iter::Iterator::size_hint
Trait std::iter::FromIterator
trait method std::iter::Extend::extend
method std::option::Option::is_some
method slice::last_mut