Add InternalEntry for use in all searches.
This commit is contained in:
parent
c71f720d9b
commit
a619fdd2ad
2 changed files with 119 additions and 167 deletions
|
|
@ -9,7 +9,6 @@
|
|||
// except according to those terms.
|
||||
|
||||
use self::Entry::*;
|
||||
use self::SearchResult::*;
|
||||
use self::VacantEntryState::*;
|
||||
|
||||
use borrow::Borrow;
|
||||
|
|
@ -26,7 +25,6 @@ use super::table::{
|
|||
Bucket,
|
||||
EmptyBucket,
|
||||
FullBucket,
|
||||
FullBucketImm,
|
||||
FullBucketMut,
|
||||
RawTable,
|
||||
SafeHash
|
||||
|
|
@ -342,10 +340,11 @@ pub struct HashMap<K, V, S = RandomState> {
|
|||
}
|
||||
|
||||
/// Search for a pre-hashed key.
|
||||
#[inline]
|
||||
fn search_hashed<K, V, M, F>(table: M,
|
||||
hash: SafeHash,
|
||||
mut is_match: F)
|
||||
-> SearchResult<K, V, M> where
|
||||
-> InternalEntry<K, V, M> where
|
||||
M: Deref<Target=RawTable<K, V>>,
|
||||
F: FnMut(&K) -> bool,
|
||||
{
|
||||
|
|
@ -353,37 +352,50 @@ fn search_hashed<K, V, M, F>(table: M,
|
|||
// undefined behavior when Bucket::new gets the raw bucket in this
|
||||
// case, immediately return the appropriate search result.
|
||||
if table.capacity() == 0 {
|
||||
return TableRef(table);
|
||||
return InternalEntry::TableIsEmpty;
|
||||
}
|
||||
|
||||
let size = table.size();
|
||||
let size = table.size() as isize;
|
||||
let mut probe = Bucket::new(table, hash);
|
||||
let ib = probe.index();
|
||||
let ib = probe.index() as isize;
|
||||
|
||||
while probe.index() != ib + size {
|
||||
loop {
|
||||
let full = match probe.peek() {
|
||||
Empty(b) => return TableRef(b.into_table()), // hit an empty bucket
|
||||
Full(b) => b
|
||||
Empty(bucket) => {
|
||||
// Found a hole!
|
||||
return InternalEntry::Vacant {
|
||||
hash: hash,
|
||||
elem: NoElem(bucket),
|
||||
};
|
||||
}
|
||||
Full(bucket) => bucket
|
||||
};
|
||||
|
||||
if full.distance() + ib < full.index() {
|
||||
let robin_ib = full.index() as isize - full.displacement() as isize;
|
||||
|
||||
if ib < robin_ib {
|
||||
// Found a luckier bucket than me.
|
||||
// We can finish the search early if we hit any bucket
|
||||
// with a lower distance to initial bucket than we've probed.
|
||||
return TableRef(full.into_table());
|
||||
return InternalEntry::Vacant {
|
||||
hash: hash,
|
||||
elem: NeqElem(full, robin_ib as usize),
|
||||
};
|
||||
}
|
||||
|
||||
// If the hash doesn't match, it can't be this one..
|
||||
if hash == full.hash() {
|
||||
// If the key doesn't match, it can't be this one..
|
||||
if is_match(full.read().0) {
|
||||
return FoundExisting(full);
|
||||
return InternalEntry::Occupied {
|
||||
elem: full
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
probe = full.next();
|
||||
assert!(probe.index() as isize != ib + size + 1);
|
||||
}
|
||||
|
||||
TableRef(probe.into_table())
|
||||
}
|
||||
|
||||
fn pop_internal<K, V>(starting_bucket: FullBucketMut<K, V>) -> (K, V) {
|
||||
|
|
@ -462,25 +474,6 @@ fn robin_hood<'a, K: 'a, V: 'a>(mut bucket: FullBucketMut<'a, K, V>,
|
|||
}
|
||||
}
|
||||
|
||||
/// A result that works like Option<FullBucket<..>> but preserves
|
||||
/// the reference that grants us access to the table in any case.
|
||||
enum SearchResult<K, V, M> {
|
||||
// This is an entry that holds the given key:
|
||||
FoundExisting(FullBucket<K, V, M>),
|
||||
|
||||
// There was no such entry. The reference is given back:
|
||||
TableRef(M)
|
||||
}
|
||||
|
||||
impl<K, V, M> SearchResult<K, V, M> {
|
||||
fn into_option(self) -> Option<FullBucket<K, V, M>> {
|
||||
match self {
|
||||
FoundExisting(bucket) => Some(bucket),
|
||||
TableRef(_) => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V, S> HashMap<K, V, S>
|
||||
where K: Eq + Hash, S: BuildHasher
|
||||
{
|
||||
|
|
@ -491,20 +484,20 @@ impl<K, V, S> HashMap<K, V, S>
|
|||
/// Search for a key, yielding the index if it's found in the hashtable.
|
||||
/// If you already have the hash for the key lying around, use
|
||||
/// search_hashed.
|
||||
fn search<'a, Q: ?Sized>(&'a self, q: &Q) -> Option<FullBucketImm<'a, K, V>>
|
||||
#[inline]
|
||||
fn search<'a, Q: ?Sized>(&'a self, q: &Q) -> InternalEntry<K, V, &'a RawTable<K, V>>
|
||||
where K: Borrow<Q>, Q: Eq + Hash
|
||||
{
|
||||
let hash = self.make_hash(q);
|
||||
search_hashed(&self.table, hash, |k| q.eq(k.borrow()))
|
||||
.into_option()
|
||||
}
|
||||
|
||||
fn search_mut<'a, Q: ?Sized>(&'a mut self, q: &Q) -> Option<FullBucketMut<'a, K, V>>
|
||||
#[inline]
|
||||
fn search_mut<'a, Q: ?Sized>(&'a mut self, q: &Q) -> InternalEntry<K, V, &'a mut RawTable<K, V>>
|
||||
where K: Borrow<Q>, Q: Eq + Hash
|
||||
{
|
||||
let hash = self.make_hash(q);
|
||||
search_hashed(&mut self.table, hash, |k| q.eq(k.borrow()))
|
||||
.into_option()
|
||||
}
|
||||
|
||||
// The caller should ensure that invariants by Robin Hood Hashing hold.
|
||||
|
|
@ -824,53 +817,19 @@ impl<K, V, S> HashMap<K, V, S>
|
|||
///
|
||||
/// If the key already exists, the hashtable will be returned untouched
|
||||
/// and a reference to the existing element will be returned.
|
||||
fn insert_hashed_nocheck(&mut self, hash: SafeHash, k: K, v: V) -> &mut V {
|
||||
self.insert_or_replace_with(hash, k, v, |_, _, _, _| ())
|
||||
}
|
||||
|
||||
fn insert_or_replace_with<'a, F>(&'a mut self,
|
||||
hash: SafeHash,
|
||||
k: K,
|
||||
v: V,
|
||||
mut found_existing: F)
|
||||
-> &'a mut V where
|
||||
F: FnMut(&mut K, &mut V, K, V),
|
||||
{
|
||||
// Worst case, we'll find one empty bucket among `size + 1` buckets.
|
||||
let size = self.table.size();
|
||||
let mut probe = Bucket::new(&mut self.table, hash);
|
||||
let ib = probe.index();
|
||||
|
||||
loop {
|
||||
let mut bucket = match probe.peek() {
|
||||
Empty(bucket) => {
|
||||
// Found a hole!
|
||||
return bucket.put(hash, k, v).into_mut_refs().1;
|
||||
}
|
||||
Full(bucket) => bucket
|
||||
};
|
||||
|
||||
// hash matches?
|
||||
if bucket.hash() == hash {
|
||||
// key matches?
|
||||
if k == *bucket.read_mut().0 {
|
||||
let (bucket_k, bucket_v) = bucket.into_mut_refs();
|
||||
debug_assert!(k == *bucket_k);
|
||||
// Key already exists. Get its reference.
|
||||
found_existing(bucket_k, bucket_v, k, v);
|
||||
return bucket_v;
|
||||
}
|
||||
fn insert_hashed_nocheck(&mut self, hash: SafeHash, k: K, v: V) -> Option<V> {
|
||||
let entry = search_hashed(&mut self.table, hash, |key| *key == k).into_entry(k);
|
||||
match entry {
|
||||
Some(Occupied(mut elem)) => {
|
||||
Some(elem.insert(v))
|
||||
}
|
||||
|
||||
let robin_ib = bucket.index() as isize - bucket.distance() as isize;
|
||||
|
||||
if (ib as isize) < robin_ib {
|
||||
// Found a luckier bucket than me. Better steal his spot.
|
||||
return robin_hood(bucket, robin_ib as usize, hash, k, v);
|
||||
Some(Vacant(elem)) => {
|
||||
elem.insert(v);
|
||||
None
|
||||
}
|
||||
None => {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
probe = bucket.next();
|
||||
assert!(probe.index() != ib + size + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -997,9 +956,7 @@ impl<K, V, S> HashMap<K, V, S>
|
|||
pub fn entry(&mut self, key: K) -> Entry<K, V> {
|
||||
// Gotta resize now.
|
||||
self.reserve(1);
|
||||
|
||||
let hash = self.make_hash(&key);
|
||||
search_entry_hashed(&mut self.table, hash, key)
|
||||
self.search_mut(&key).into_entry(key).expect("unreachable")
|
||||
}
|
||||
|
||||
/// Returns the number of elements in the map.
|
||||
|
|
@ -1102,7 +1059,7 @@ impl<K, V, S> HashMap<K, V, S>
|
|||
pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V>
|
||||
where K: Borrow<Q>, Q: Hash + Eq
|
||||
{
|
||||
self.search(k).map(|bucket| bucket.into_refs().1)
|
||||
self.search(k).into_occupied_bucket().map(|bucket| bucket.into_refs().1)
|
||||
}
|
||||
|
||||
/// Returns true if the map contains a value for the specified key.
|
||||
|
|
@ -1125,7 +1082,7 @@ impl<K, V, S> HashMap<K, V, S>
|
|||
pub fn contains_key<Q: ?Sized>(&self, k: &Q) -> bool
|
||||
where K: Borrow<Q>, Q: Hash + Eq
|
||||
{
|
||||
self.search(k).is_some()
|
||||
self.search(k).into_occupied_bucket().is_some()
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the value corresponding to the key.
|
||||
|
|
@ -1150,7 +1107,7 @@ impl<K, V, S> HashMap<K, V, S>
|
|||
pub fn get_mut<Q: ?Sized>(&mut self, k: &Q) -> Option<&mut V>
|
||||
where K: Borrow<Q>, Q: Hash + Eq
|
||||
{
|
||||
self.search_mut(k).map(|bucket| bucket.into_mut_refs().1)
|
||||
self.search_mut(k).into_occupied_bucket().map(|bucket| bucket.into_mut_refs().1)
|
||||
}
|
||||
|
||||
/// Inserts a key-value pair into the map.
|
||||
|
|
@ -1181,12 +1138,7 @@ impl<K, V, S> HashMap<K, V, S>
|
|||
pub fn insert(&mut self, k: K, v: V) -> Option<V> {
|
||||
let hash = self.make_hash(&k);
|
||||
self.reserve(1);
|
||||
|
||||
let mut retval = None;
|
||||
self.insert_or_replace_with(hash, k, v, |_, val_ref, _, val| {
|
||||
retval = Some(replace(val_ref, val));
|
||||
});
|
||||
retval
|
||||
self.insert_hashed_nocheck(hash, k, v)
|
||||
}
|
||||
|
||||
/// Removes a key from the map, returning the value at the key if the key
|
||||
|
|
@ -1214,54 +1166,7 @@ impl<K, V, S> HashMap<K, V, S>
|
|||
return None
|
||||
}
|
||||
|
||||
self.search_mut(k).map(|bucket| pop_internal(bucket).1)
|
||||
}
|
||||
}
|
||||
|
||||
fn search_entry_hashed<'a, K: Eq, V>(table: &'a mut RawTable<K,V>, hash: SafeHash, k: K)
|
||||
-> Entry<'a, K, V>
|
||||
{
|
||||
// Worst case, we'll find one empty bucket among `size + 1` buckets.
|
||||
let size = table.size();
|
||||
let mut probe = Bucket::new(table, hash);
|
||||
let ib = probe.index();
|
||||
|
||||
loop {
|
||||
let bucket = match probe.peek() {
|
||||
Empty(bucket) => {
|
||||
// Found a hole!
|
||||
return Vacant(VacantEntry {
|
||||
hash: hash,
|
||||
key: k,
|
||||
elem: NoElem(bucket),
|
||||
});
|
||||
},
|
||||
Full(bucket) => bucket
|
||||
};
|
||||
|
||||
// hash matches?
|
||||
if bucket.hash() == hash {
|
||||
// key matches?
|
||||
if k == *bucket.read().0 {
|
||||
return Occupied(OccupiedEntry{
|
||||
elem: bucket,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let robin_ib = bucket.index() as isize - bucket.distance() as isize;
|
||||
|
||||
if (ib as isize) < robin_ib {
|
||||
// Found a luckier bucket than me. Better steal his spot.
|
||||
return Vacant(VacantEntry {
|
||||
hash: hash,
|
||||
key: k,
|
||||
elem: NeqElem(bucket, robin_ib as usize),
|
||||
});
|
||||
}
|
||||
|
||||
probe = bucket.next();
|
||||
assert!(probe.index() != ib + size + 1);
|
||||
self.search_mut(k).into_occupied_bucket().map(|bucket| pop_internal(bucket).1)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1382,18 +1287,46 @@ pub struct Drain<'a, K: 'a, V: 'a> {
|
|||
inner: iter::Map<table::Drain<'a, K, V>, fn((SafeHash, K, V)) -> (K, V)>
|
||||
}
|
||||
|
||||
/// A view into a single occupied location in a HashMap.
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
pub struct OccupiedEntry<'a, K: 'a, V: 'a> {
|
||||
elem: FullBucket<K, V, &'a mut RawTable<K, V>>,
|
||||
enum InternalEntry<K, V, M> {
|
||||
Occupied {
|
||||
elem: FullBucket<K, V, M>,
|
||||
},
|
||||
Vacant {
|
||||
hash: SafeHash,
|
||||
elem: VacantEntryState<K, V, M>,
|
||||
},
|
||||
TableIsEmpty,
|
||||
}
|
||||
|
||||
/// A view into a single empty location in a HashMap.
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
pub struct VacantEntry<'a, K: 'a, V: 'a> {
|
||||
hash: SafeHash,
|
||||
key: K,
|
||||
elem: VacantEntryState<K, V, &'a mut RawTable<K, V>>,
|
||||
impl<K, V, M> InternalEntry<K, V, M> {
|
||||
#[inline]
|
||||
fn into_occupied_bucket(self) -> Option<FullBucket<K, V, M>> {
|
||||
match self {
|
||||
InternalEntry::Occupied { elem } => Some(elem),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K, V> InternalEntry<K, V, &'a mut RawTable<K, V>> {
|
||||
#[inline]
|
||||
fn into_entry(self, key: K) -> Option<Entry<'a, K, V>> {
|
||||
match self {
|
||||
InternalEntry::Occupied { elem } => {
|
||||
Some(Occupied(OccupiedEntry {
|
||||
elem: elem
|
||||
}))
|
||||
}
|
||||
InternalEntry::Vacant { hash, elem } => {
|
||||
Some(Vacant(VacantEntry {
|
||||
hash: hash,
|
||||
key: key,
|
||||
elem: elem,
|
||||
}))
|
||||
}
|
||||
InternalEntry::TableIsEmpty => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A view into a single location in a map, which may be vacant or occupied.
|
||||
|
|
@ -1412,6 +1345,20 @@ pub enum Entry<'a, K: 'a, V: 'a> {
|
|||
),
|
||||
}
|
||||
|
||||
/// A view into a single occupied location in a HashMap.
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
pub struct OccupiedEntry<'a, K: 'a, V: 'a> {
|
||||
elem: FullBucket<K, V, &'a mut RawTable<K, V>>,
|
||||
}
|
||||
|
||||
/// A view into a single empty location in a HashMap.
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
pub struct VacantEntry<'a, K: 'a, V: 'a> {
|
||||
hash: SafeHash,
|
||||
key: K,
|
||||
elem: VacantEntryState<K, V, &'a mut RawTable<K, V>>,
|
||||
}
|
||||
|
||||
/// Possible states of a VacantEntry.
|
||||
enum VacantEntryState<K, V, M> {
|
||||
/// The index is occupied, but the key to insert has precedence,
|
||||
|
|
@ -1703,7 +1650,7 @@ impl<K, S, Q: ?Sized> super::Recover<Q> for HashMap<K, (), S>
|
|||
type Key = K;
|
||||
|
||||
fn get(&self, key: &Q) -> Option<&K> {
|
||||
self.search(key).map(|bucket| bucket.into_refs().0)
|
||||
self.search(key).into_occupied_bucket().map(|bucket| bucket.into_refs().0)
|
||||
}
|
||||
|
||||
fn take(&mut self, key: &Q) -> Option<K> {
|
||||
|
|
@ -1711,18 +1658,21 @@ impl<K, S, Q: ?Sized> super::Recover<Q> for HashMap<K, (), S>
|
|||
return None
|
||||
}
|
||||
|
||||
self.search_mut(key).map(|bucket| pop_internal(bucket).0)
|
||||
self.search_mut(key).into_occupied_bucket().map(|bucket| pop_internal(bucket).0)
|
||||
}
|
||||
|
||||
fn replace(&mut self, key: K) -> Option<K> {
|
||||
let hash = self.make_hash(&key);
|
||||
self.reserve(1);
|
||||
|
||||
let mut retkey = None;
|
||||
self.insert_or_replace_with(hash, key, (), |key_ref, _, key, _| {
|
||||
retkey = Some(replace(key_ref, key));
|
||||
});
|
||||
retkey
|
||||
match search_hashed(&mut self.table, hash, |k| *k == key) {
|
||||
InternalEntry::Occupied { mut elem } => {
|
||||
Some(mem::replace(elem.read_mut().0, key))
|
||||
}
|
||||
_ => {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -180,6 +180,8 @@ impl<K, V> RawBucket<K, V> {
|
|||
}
|
||||
}
|
||||
|
||||
// Uncomment dead code when it's needed.
|
||||
|
||||
// Buckets hold references to the table.
|
||||
impl<K, V, M> FullBucket<K, V, M> {
|
||||
/// Borrow a reference to the table.
|
||||
|
|
@ -201,17 +203,17 @@ impl<K, V, M> EmptyBucket<K, V, M> {
|
|||
pub fn table(&self) -> &M {
|
||||
&self.table
|
||||
}
|
||||
/// Move out the reference to the table.
|
||||
pub fn into_table(self) -> M {
|
||||
self.table
|
||||
}
|
||||
// /// Move out the reference to the table.
|
||||
// pub fn into_table(self) -> M {
|
||||
// self.table
|
||||
// }
|
||||
}
|
||||
|
||||
impl<K, V, M> Bucket<K, V, M> {
|
||||
/// Move out the reference to the table.
|
||||
pub fn into_table(self) -> M {
|
||||
self.table
|
||||
}
|
||||
// /// Move out the reference to the table.
|
||||
// pub fn into_table(self) -> M {
|
||||
// self.table
|
||||
// }
|
||||
/// Get the raw index.
|
||||
pub fn index(&self) -> usize {
|
||||
self.idx
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue