Skip to content

Commit 14c3310

Browse files
committed
auto merge of #6815 : kballard/rust/hashmap-insert_or_modify_with, r=erickt
`std::hashmap::HashMap.insert_or_update_with()` is basically the opposite of `find_or_insert_with()`. It inserts a given key-value pair if the key does not already exist, or replaces the existing value with the output of the passed function if it does. This is useful because replicating this with existing functionality is awkward, especially with the current borrow-checker. In my own project I have code that looks like if match map.find_mut(&key) { None => { true } Some(x) => { *x += 1; false } } { map.insert(key, 0); } and it took several iterations to make it look this good. The new function turns this into map.insert_or_update_with(key, 0, |_,x| *x += 1);
2 parents 96f6f29 + 75f1b7f commit 14c3310

File tree

1 file changed

+35
-36
lines changed

1 file changed

+35
-36
lines changed

src/libstd/hashmap.rs

+35-36
Original file line numberDiff line numberDiff line change
@@ -425,9 +425,10 @@ impl<K: Hash + Eq, V> HashMap<K, V> {
425425
}
426426
}
427427

428-
/// Return the value corresponding to the key in the map, or insert
429-
/// and return the value if it doesn't exist.
430-
pub fn find_or_insert<'a>(&'a mut self, k: K, v: V) -> &'a V {
428+
/// Modify and return the value corresponding to the key in the map, or
429+
/// insert and return a new value if it doesn't exist.
430+
pub fn mangle<'a,A>(&'a mut self, k: K, a: A, not_found: &fn(&K, A) -> V,
431+
found: &fn(&K, &mut V, A)) -> &'a mut V {
431432
if self.size >= self.resize_at {
432433
// n.b.: We could also do this after searching, so
433434
// that we do not resize if this call to insert is
@@ -441,46 +442,37 @@ impl<K: Hash + Eq, V> HashMap<K, V> {
441442
let hash = k.hash_keyed(self.k0, self.k1) as uint;
442443
let idx = match self.bucket_for_key_with_hash(hash, &k) {
443444
TableFull => fail!("Internal logic error"),
444-
FoundEntry(idx) => idx,
445+
FoundEntry(idx) => { found(&k, self.mut_value_for_bucket(idx), a); idx }
445446
FoundHole(idx) => {
446-
self.buckets[idx] = Some(Bucket{hash: hash, key: k,
447-
value: v});
447+
let v = not_found(&k, a);
448+
self.buckets[idx] = Some(Bucket{hash: hash, key: k, value: v});
448449
self.size += 1;
449450
idx
450-
},
451+
}
451452
};
452453

453-
self.value_for_bucket(idx)
454+
self.mut_value_for_bucket(idx)
455+
}
456+
457+
/// Return the value corresponding to the key in the map, or insert
458+
/// and return the value if it doesn't exist.
459+
pub fn find_or_insert<'a>(&'a mut self, k: K, v: V) -> &'a mut V {
460+
self.mangle(k, v, |_k, a| a, |_k,_v,_a| ())
454461
}
455462

456463
/// Return the value corresponding to the key in the map, or create,
457464
/// insert, and return a new value if it doesn't exist.
458465
pub fn find_or_insert_with<'a>(&'a mut self, k: K, f: &fn(&K) -> V)
459-
-> &'a V {
460-
if self.size >= self.resize_at {
461-
// n.b.: We could also do this after searching, so
462-
// that we do not resize if this call to insert is
463-
// simply going to update a key in place. My sense
464-
// though is that it's worse to have to search through
465-
// buckets to find the right spot twice than to just
466-
// resize in this corner case.
467-
self.expand();
468-
}
469-
470-
let hash = k.hash_keyed(self.k0, self.k1) as uint;
471-
let idx = match self.bucket_for_key_with_hash(hash, &k) {
472-
TableFull => fail!("Internal logic error"),
473-
FoundEntry(idx) => idx,
474-
FoundHole(idx) => {
475-
let v = f(&k);
476-
self.buckets[idx] = Some(Bucket{hash: hash, key: k,
477-
value: v});
478-
self.size += 1;
479-
idx
480-
},
481-
};
466+
-> &'a mut V {
467+
self.mangle(k, (), |k,_a| f(k), |_k,_v,_a| ())
468+
}
482469

483-
self.value_for_bucket(idx)
470+
/// Insert a key-value pair into the map if the key is not already present.
471+
/// Otherwise, modify the existing value for the key.
472+
/// Returns the new or modified value for the key.
473+
pub fn insert_or_update_with<'a>(&'a mut self, k: K, v: V,
474+
f: &fn(&K, &mut V)) -> &'a mut V {
475+
self.mangle(k, v, |_k,a| a, |k,v,_a| f(k,v))
484476
}
485477

486478
/// Calls a function on each element of a hash map, destroying the hash
@@ -763,15 +755,22 @@ mod test_map {
763755
#[test]
764756
fn test_find_or_insert() {
765757
let mut m = HashMap::new::<int, int>();
766-
assert_eq!(m.find_or_insert(1, 2), &2);
767-
assert_eq!(m.find_or_insert(1, 3), &2);
758+
assert_eq!(*m.find_or_insert(1, 2), 2);
759+
assert_eq!(*m.find_or_insert(1, 3), 2);
768760
}
769761

770762
#[test]
771763
fn test_find_or_insert_with() {
772764
let mut m = HashMap::new::<int, int>();
773-
assert_eq!(m.find_or_insert_with(1, |_| 2), &2);
774-
assert_eq!(m.find_or_insert_with(1, |_| 3), &2);
765+
assert_eq!(*m.find_or_insert_with(1, |_| 2), 2);
766+
assert_eq!(*m.find_or_insert_with(1, |_| 3), 2);
767+
}
768+
769+
#[test]
770+
fn test_insert_or_update_with() {
771+
let mut m = HashMap::new::<int, int>();
772+
assert_eq!(*m.insert_or_update_with(1, 2, |_,x| *x+=1), 2);
773+
assert_eq!(*m.insert_or_update_with(1, 2, |_,x| *x+=1), 3);
775774
}
776775

777776
#[test]

0 commit comments

Comments
 (0)