HashMap实现get和insert方法,它们分别采用单个不可变借位和单个值的移动.
我想要一个像这样的特性,但需要两个键而不是一个.它使用里面的地图,但它只是实现的细节.
pub struct Table<A: Eq + Hash, B: Eq + Hash> {
map: HashMap<(A, B), f64>,
}
impl<A: Eq + Hash, B: Eq + Hash> Memory<A, B> for Table<A, B> {
fn get(&self, a: &A, b: &B) -> f64 {
let key: &(A, B) = ??;
*self.map.get(key).unwrap()
}
fn set(&mut self, a: A, b: B, v: f64) {
self.map.insert((a, b), v);
}
}
Run Code Online (Sandbox Code Playgroud)
ken*_*ytm 24
这当然是可能的.该签名get就是
fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: Hash + Eq,
Run Code Online (Sandbox Code Playgroud)
这里的问题是实现这样的&Q类型
(A, B): Borrow<Q>Q 器物 Hash + Eq为了满足条件(1),我们需要考虑如何写
fn borrow(self: &(A, B)) -> &Q
Run Code Online (Sandbox Code Playgroud)
诀窍是&Q 不需要是一个简单的指针,它可以是一个特质对象!我们的想法是创建一个Q具有两个实现的特征:
impl Q for (A, B)
impl Q for (&A, &B)
Run Code Online (Sandbox Code Playgroud)
该Borrow实施将简单的返回self,我们可以构建一个&Q由独立的两个元素特质的对象.
在全面实施是这样的:
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::borrow::Borrow;
// See explanation (1).
#[derive(PartialEq, Eq, Hash)]
struct Pair<A, B>(A, B);
#[derive(PartialEq, Eq, Hash)]
struct BorrowedPair<'a, 'b, A: 'a, B: 'b>(&'a A, &'b B);
// See explanation (2).
trait KeyPair<A, B> {
/// Obtains the first element of the pair.
fn a(&self) -> &A;
/// Obtains the second element of the pair.
fn b(&self) -> &B;
}
// See explanation (3).
impl<'a, A, B> Borrow<KeyPair<A, B> + 'a> for Pair<A, B>
where
A: Eq + Hash + 'a,
B: Eq + Hash + 'a,
{
fn borrow(&self) -> &(KeyPair<A, B> + 'a) {
self
}
}
// See explanation (4).
impl<'a, A: Hash, B: Hash> Hash for (KeyPair<A, B> + 'a) {
fn hash<H: Hasher>(&self, state: &mut H) {
self.a().hash(state);
self.b().hash(state);
}
}
impl<'a, A: Eq, B: Eq> PartialEq for (KeyPair<A, B> + 'a) {
fn eq(&self, other: &Self) -> bool {
self.a() == other.a() && self.b() == other.b()
}
}
impl<'a, A: Eq, B: Eq> Eq for (KeyPair<A, B> + 'a) {}
// OP's Table struct
pub struct Table<A: Eq + Hash, B: Eq + Hash> {
map: HashMap<Pair<A, B>, f64>,
}
impl<A: Eq + Hash, B: Eq + Hash> Table<A, B> {
fn new() -> Self {
Table { map: HashMap::new() }
}
fn get(&self, a: &A, b: &B) -> f64 {
*self.map.get(&BorrowedPair(a, b) as &KeyPair<A, B>).unwrap()
}
fn set(&mut self, a: A, b: B, v: f64) {
self.map.insert(Pair(a, b), v);
}
}
// Boring stuff below.
impl<A, B> KeyPair<A, B> for Pair<A, B>
where
A: Eq + Hash,
B: Eq + Hash,
{
fn a(&self) -> &A {
&self.0
}
fn b(&self) -> &B {
&self.1
}
}
impl<'a, 'b, A, B> KeyPair<A, B> for BorrowedPair<'a, 'b, A, B>
where
A: Eq + Hash + 'a,
B: Eq + Hash + 'b,
{
fn a(&self) -> &A {
self.0
}
fn b(&self) -> &B {
self.1
}
}
//----------------------------------------------------------------
#[derive(Eq, PartialEq, Hash)]
struct A(&'static str);
#[derive(Eq, PartialEq, Hash)]
struct B(&'static str);
fn main() {
let mut table = Table::new();
table.set(A("abc"), B("def"), 4.0);
table.set(A("123"), B("456"), 45.0);
println!("{:?} == 45.0?", table.get(&A("123"), &B("456")));
println!("{:?} == 4.0?", table.get(&A("abc"), &B("def")));
// Should panic below.
println!("{:?} == NaN?", table.get(&A("123"), &B("def")));
}
Run Code Online (Sandbox Code Playgroud)
说明:
我们介绍了Pair和BorrowedPair类型.(A, B)由于孤儿规则E0210,我们无法直接使用.这很好,因为地图是一个实现细节.
这个KeyPair特性扮演了Q我们上面提到的角色.我们需要impl Eq + Hash for KeyPair,但Eq并且Hash都不是对象安全的.我们添加a()和b()方法来帮助手动实现它们.
现在我们实行Borrow的特质Pair<A, B>来KeyPair + 'a.注意'a- 这是Table::get实际工作所需的微妙位.任意性'a允许我们说a Pair<A, B>可以在任何生命周期中借用到特征对象.如果我们没有指定'a,那么unsized trait对象将默认为'static,这意味着Borrow只有当实现像BorrowedPairoutlives 时才能应用trait 'static,这当然不是这种情况.
最后,我们实施Eq和Hash.如上所述,我们实现KeyPair + 'a而不是KeyPair(KeyPair + 'static在这种情况下意味着).
在计算哈希值和检查相等性时,使用特征对象会产生间接成本get().如果优化器能够将其虚拟化,则可以消除成本,但LLVM是否会执行此操作尚不清楚.
另一种方法是将地图存储为HashMap<(Cow<A>, Cow<B>), f64>.使用此需要较少的"聪明代码",但现在有存储资/借来的标志,以及在运行时的成本存储成本get()和set().
除非你分叉标准HashMap并添加一个Hash + Eq单独查找条目的方法,否则没有保证零成本的解决方案.
在该get方法中,在存储器中借用的值a和b可能彼此不相邻的值.
[--- A ---] other random stuff in between [--- B ---]
\ /
&a points to here &b points to here
Run Code Online (Sandbox Code Playgroud)
借用类型的值&(A, B)将需要a A和a B相邻.
[--- A ---][--- B ---]
\
we could have a borrow of type &(A, B) pointing to here
Run Code Online (Sandbox Code Playgroud)
一些不安全的代码可以解决这个问题!我们需要一个浅的副本*a和*b.
use std::collections::HashMap;
use std::hash::Hash;
use std::mem::ManuallyDrop;
use std::ptr;
#[derive(Debug)]
pub struct Table<A: Eq + Hash, B: Eq + Hash> {
map: HashMap<(A, B), f64>
}
impl<A: Eq + Hash, B: Eq + Hash> Table<A, B> {
fn get(&self, a: &A, b: &B) -> f64 {
unsafe {
// The values `a` and `b` may not be adjacent in memory. Perform a
// shallow copy to make them adjacent. This should be fast! This is
// not a deep copy, so for example if the type `A` is `String` then
// only the pointer/length/capacity are copied, not any of the data.
//
// This makes a `(A, B)` backed by the same data as `a` and `b`.
let k = (ptr::read(a), ptr::read(b));
// Make sure not to drop our `(A, B)`, even if `get` panics. The
// caller or whoever owns `a` and `b` will drop them.
let k = ManuallyDrop::new(k);
// Deref `k` to get `&(A, B)` and perform lookup.
let v = self.map.get(&k);
// Turn `Option<&f64>` into `f64`.
*v.unwrap()
}
}
fn set(&mut self, a: A, b: B, v: f64) {
self.map.insert((a, b), v);
}
}
fn main() {
let mut table = Table { map: HashMap::new() };
table.set(true, true, 1.0);
table.set(true, false, 2.0);
println!("{:#?}", table);
let v = table.get(&true, &true);
assert_eq!(v, 1.0);
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1949 次 |
| 最近记录: |