Skip to content
Permalink
Browse files

Allow collection queries without key references

For both BTree and HashMap, it is possible to have keys whose full value
is not used in the equality check.  It is sometimes useful to look up
items in the map using a newtype that can hash or compare itself with
actual keys, without being a valid key itself.

This extends (some methods of) these collections to allow more flexible
queries.  Currently an RFC.

Signed-off-by: Daniel De Graaf <code@danieldg.net>
  • Loading branch information...
danieldg committed Sep 3, 2017
1 parent c8642da commit e3ec035c338927f277ca7d4509587185e6cac1f1
@@ -8,7 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use core::cmp::Ordering;
use core::cmp::{Ordering, OrdWith};
use core::fmt::Debug;
use core::hash::{Hash, Hasher};
use core::iter::{FromIterator, Peekable, FusedIterator};
@@ -218,8 +218,7 @@ impl<K: Clone, V: Clone> Clone for BTreeMap<K, V> {
}

impl<K, Q: ?Sized> super::Recover<Q> for BTreeMap<K, ()>
where K: Borrow<Q> + Ord,
Q: Ord
where Q: OrdWith<K>, K : Ord
{
type Key = K;

@@ -576,6 +575,17 @@ impl<K: Ord, V> BTreeMap<K, V> {
}
}

/// Same as get() but better
#[unstable(feature = "extended_search_types", issue="0")]
pub fn get_by<Q: ?Sized>(&self, key: &Q) -> Option<&V>
where Q: OrdWith<K>
{
match search::search_tree(self.root.as_ref(), key) {
Found(handle) => Some(handle.into_kv().1),
GoDown(_) => None,
}
}

/// Returns `true` if the map contains a value for the specified key.
///
/// The key may be any borrowed form of the map's key type, but the ordering
@@ -632,6 +642,17 @@ impl<K: Ord, V> BTreeMap<K, V> {
}
}

/// Same as get_mut() but accepts more types as key
#[unstable(feature = "extended_search_types", issue="0")]
pub fn get_mut_by<Q: ?Sized>(&mut self, key: &Q) -> Option<&mut V>
where Q: OrdWith<K>
{
match search::search_tree(self.root.as_mut(), key) {
Found(handle) => Some(handle.into_kv_mut().1),
GoDown(_) => None,
}
}

/// Inserts a key-value pair into the map.
///
/// If the map did not have this key present, `None` is returned.
@@ -705,6 +726,24 @@ impl<K: Ord, V> BTreeMap<K, V> {
}
}

/// Same as remove() but accepts more search types
#[unstable(feature = "extended_search_types", issue="0")]
pub fn remove_by<Q: ?Sized>(&mut self, key: &Q) -> Option<V>
where Q: OrdWith<K>
{
match search::search_tree(self.root.as_mut(), key) {
Found(handle) => {
Some(OccupiedEntry {
handle,
length: &mut self.length,
_marker: PhantomData,
}
.remove())
}
GoDown(_) => None,
}
}

/// Moves all elements from `other` into `Self`, leaving `other` empty.
///
/// # Examples
@@ -8,9 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use core::cmp::Ordering;

use borrow::Borrow;
use core::cmp::{Ordering, OrdWith};

use super::node::{Handle, NodeRef, marker};

@@ -26,7 +24,7 @@ pub fn search_tree<BorrowType, K, V, Q: ?Sized>(
mut node: NodeRef<BorrowType, K, V, marker::LeafOrInternal>,
key: &Q
) -> SearchResult<BorrowType, K, V, marker::LeafOrInternal, marker::Leaf>
where Q: Ord, K: Borrow<Q> {
where Q: OrdWith<K> {

loop {
match search_node(node, key) {
@@ -46,7 +44,7 @@ pub fn search_node<BorrowType, K, V, Type, Q: ?Sized>(
node: NodeRef<BorrowType, K, V, Type>,
key: &Q
) -> SearchResult<BorrowType, K, V, Type, Type>
where Q: Ord, K: Borrow<Q> {
where Q: OrdWith<K> {

match search_linear(&node, key) {
(idx, true) => Found(
@@ -62,10 +60,10 @@ pub fn search_linear<BorrowType, K, V, Type, Q: ?Sized>(
node: &NodeRef<BorrowType, K, V, Type>,
key: &Q
) -> (usize, bool)
where Q: Ord, K: Borrow<Q> {
where Q: OrdWith<K> {

for (i, k) in node.keys().iter().enumerate() {
match key.cmp(k.borrow()) {
match key.cmp_with(k) {
Ordering::Greater => {},
Ordering::Equal => return (i, true),
Ordering::Less => return (i, false)
@@ -614,7 +614,7 @@ impl<T: Ord> BTreeSet<T> {
/// one. Returns the replaced value.
#[stable(feature = "set_recovery", since = "1.9.0")]
pub fn replace(&mut self, value: T) -> Option<T> {
Recover::replace(&mut self.map, value)
<_ as Recover<T>>::replace(&mut self.map, value)
}

/// Removes a value from the set. Returns `true` if the value was
@@ -92,6 +92,7 @@
#![feature(custom_attribute)]
#![feature(dropck_eyepatch)]
#![feature(exact_size_is_empty)]
#![feature(extended_search_types)]
#![feature(fmt_internals)]
#![feature(fundamental)]
#![feature(fused)]
@@ -758,6 +758,33 @@ pub fn max<T: Ord>(v1: T, v2: T) -> T {
v1.max(v2)
}

/// Provides a total ordering across two types
#[unstable(feature = "extended_search_types", issue="0")]
pub trait OrdWith<Rhs : ?Sized> {
/// This method returns an `Ordering` between `self` and `other`.
///
/// By convention, `self.cmp(&other)` returns the ordering matching the expression
/// `self <operator> other` if true.
///
/// # Examples
///
/// ```
/// use std::cmp::Ordering;
///
/// assert_eq!(5.cmp(&10), Ordering::Less);
/// assert_eq!(10.cmp(&5), Ordering::Greater);
/// assert_eq!(5.cmp(&5), Ordering::Equal);
/// ```
fn cmp_with(&self, other : &Rhs) -> Ordering;
}

#[unstable(feature = "extended_search_types", issue="0")]
impl<K : ?Sized, Q : ?Sized> OrdWith<K> for Q where K : super::borrow::Borrow<Q>, Q : Ord {
fn cmp_with(&self, other : &K) -> Ordering {
self.cmp(other.borrow())
}
}

// Implementation of PartialEq, Eq, PartialOrd and Ord for primitive types
mod impls {
use cmp::Ordering::{self, Less, Greater, Equal};
@@ -495,6 +495,20 @@ impl<H> Default for BuildHasherDefault<H> {
}
}

/// If you impl this trait, any equal items between Self and Rhs must have matching hashes
#[unstable(feature = "extended_search_types", issue="0")]
pub trait HashEq<Rhs : ?Sized> : Hash {
/// Return true if Self and other are equal
fn eq(&self, other : &Rhs) -> bool;
}

#[unstable(feature = "extended_search_types", issue="0")]
impl<K : ?Sized, Q : ?Sized> HashEq<K> for Q where K : super::borrow::Borrow<Q>, Q : Hash + Eq {
fn eq(&self, other : &K) -> bool {
<Q as PartialEq>::eq(self, other.borrow())
}
}

//////////////////////////////////////////////////////////////////////////////

mod impls {
@@ -16,7 +16,7 @@ use borrow::Borrow;
use cmp::max;
use fmt::{self, Debug};
#[allow(deprecated)]
use hash::{Hash, Hasher, BuildHasher, SipHasher13};
use hash::{Hash, Hasher, HashEq, BuildHasher, SipHasher13};
use iter::{FromIterator, FusedIterator};
use mem::{self, replace};
use ops::{Deref, Index, InPlace, Place, Placer};
@@ -547,20 +547,18 @@ impl<K, V, S> HashMap<K, V, S>
/// search_hashed.
#[inline]
fn search<'a, Q: ?Sized>(&'a self, q: &Q) -> InternalEntry<K, V, &'a RawTable<K, V>>
where K: Borrow<Q>,
Q: Eq + Hash
where Q: HashEq<K>
{
let hash = self.make_hash(q);
search_hashed(&self.table, hash, |k| q.eq(k.borrow()))
search_hashed(&self.table, hash, |k| q.eq(k))
}

#[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
where Q: HashEq<K>
{
let hash = self.make_hash(q);
search_hashed(&mut self.table, hash, |k| q.eq(k.borrow()))
search_hashed(&mut self.table, hash, |k| q.eq(k))
}

// The caller should ensure that invariants by Robin Hood Hashing hold
@@ -1106,6 +1104,14 @@ impl<K, V, S> HashMap<K, V, S>
self.search(k).into_occupied_bucket().map(|bucket| bucket.into_refs().1)
}

/// Same as get() but allows more possible types in the query
#[unstable(feature = "extended_search_types", issue="0")]
pub fn get_by<Q: ?Sized>(&self, k: &Q) -> Option<&V>
where Q: HashEq<K>
{
self.search(k).into_occupied_bucket().map(|bucket| bucket.into_refs().1)
}

/// Returns true if the map contains a value for the specified key.
///
/// The key may be any borrowed form of the map's key type, but
@@ -1162,6 +1168,14 @@ impl<K, V, S> HashMap<K, V, S>
self.search_mut(k).into_occupied_bucket().map(|bucket| bucket.into_mut_refs().1)
}

/// Same as get_mut() but allows more possible types in the query
#[unstable(feature = "extended_search_types", issue="0")]
pub fn get_mut_by<Q: ?Sized>(&mut self, k: &Q) -> Option<&mut V>
where Q: HashEq<K>
{
self.search_mut(k).into_occupied_bucket().map(|bucket| bucket.into_mut_refs().1)
}

/// Inserts a key-value pair into the map.
///
/// If the map did not have this key present, [`None`] is returned.
@@ -1226,6 +1240,21 @@ impl<K, V, S> HashMap<K, V, S>
self.search_mut(k).into_occupied_bucket().map(|bucket| pop_internal(bucket).1)
}

/// Same as remove() but allows more possible types in the query and returns the key
#[unstable(feature = "extended_search_types", issue="0")]
pub fn remove_by<Q: ?Sized>(&mut self, k: &Q) -> Option<(K,V)>
where Q: HashEq<K>
{
if self.table.size() == 0 {
return None;
}

self.search_mut(k).into_occupied_bucket().map(|bucket| {
let (key, val, _) = pop_internal(bucket);
(key, val)
})
}

/// Retains only the elements specified by the predicate.
///
/// In other words, remove all pairs `(k, v)` such that `f(&k,&mut v)` returns `false`.
@@ -2442,9 +2471,9 @@ impl fmt::Debug for RandomState {
}

impl<K, S, Q: ?Sized> super::Recover<Q> for HashMap<K, (), S>
where K: Eq + Hash + Borrow<Q>,
where K: Eq + Hash,
S: BuildHasher,
Q: Eq + Hash
Q: HashEq<K>
{
type Key = K;

@@ -10,7 +10,7 @@

use borrow::Borrow;
use fmt;
use hash::{Hash, BuildHasher};
use hash::{Hash, HashEq, BuildHasher};
use iter::{Chain, FromIterator, FusedIterator};
use ops::{BitOr, BitAnd, BitXor, Sub};

@@ -534,6 +534,14 @@ impl<T, S> HashSet<T, S>
Recover::get(&self.map, value)
}

/// Same as get() but allows more possible types in the query
#[unstable(feature = "extended_search_types", issue="0")]
pub fn get_by<Q: ?Sized>(&self, value: &Q) -> Option<&T>
where Q: HashEq<T>
{
Recover::get(&self.map, value)
}

/// Returns `true` if `self` has no elements in common with `other`.
/// This is equivalent to checking for an empty intersection.
///
@@ -630,7 +638,7 @@ impl<T, S> HashSet<T, S>
/// one. Returns the replaced value.
#[stable(feature = "set_recovery", since = "1.9.0")]
pub fn replace(&mut self, value: T) -> Option<T> {
Recover::replace(&mut self.map, value)
<_ as Recover<T>>::replace(&mut self.map, value)
}

/// Removes a value from the set. Returns `true` if the value was
@@ -678,6 +686,14 @@ impl<T, S> HashSet<T, S>
Recover::take(&mut self.map, value)
}

/// Same as take() but allows more possible types in the search
#[unstable(feature = "extended_search_types", issue="0")]
pub fn take_by<Q: ?Sized>(&mut self, value: &Q) -> Option<T>
where Q: HashEq<T>
{
Recover::take(&mut self.map, value)
}

/// Retains only the elements specified by the predicate.
///
/// In other words, remove all elements `e` such that `f(&e)` returns `false`.
@@ -256,6 +256,7 @@
#![feature(core_intrinsics)]
#![feature(dropck_eyepatch)]
#![feature(exact_size_is_empty)]
#![feature(extended_search_types)]
#![feature(float_from_str_radix)]
#![feature(fn_traits)]
#![feature(fnbox)]

0 comments on commit e3ec035

Please sign in to comment.
You can’t perform that action at this time.