Add InternalEntry for use in all searches.

This commit is contained in:
Piotr Czarnecki 2016-03-05 22:11:15 +01:00
parent c71f720d9b
commit a619fdd2ad
2 changed files with 119 additions and 167 deletions

View file

@ -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
}
}
}
}

View file

@ -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