Skip to content

Commit 8ddc4f1

Browse files
authored
Merge pull request #142 from cuviper/drain-ranges
Expand drain to accept any RangeBounds<usize>
2 parents 884a104 + c42867c commit 8ddc4f1

File tree

6 files changed

+165
-13
lines changed

6 files changed

+165
-13
lines changed

src/map.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use ::core::cmp::Ordering;
1313
use ::core::fmt;
1414
use ::core::hash::{BuildHasher, Hash, Hasher};
1515
use ::core::iter::FromIterator;
16-
use ::core::ops::{Index, IndexMut, RangeFull};
16+
use ::core::ops::{Index, IndexMut, RangeBounds};
1717
use ::core::slice::{Iter as SliceIter, IterMut as SliceIterMut};
1818

1919
#[cfg(has_std)]
@@ -252,9 +252,23 @@ impl<K, V, S> IndexMap<K, V, S> {
252252
self.core.clear();
253253
}
254254

255-
/// Clears the `IndexMap`, returning all key-value pairs as a drain iterator.
256-
/// Keeps the allocated memory for reuse.
257-
pub fn drain(&mut self, range: RangeFull) -> Drain<'_, K, V> {
255+
/// Clears the `IndexMap` in the given index range, returning those
256+
/// key-value pairs as a drain iterator.
257+
///
258+
/// The range may be any type that implements `RangeBounds<usize>`,
259+
/// including all of the `std::ops::Range*` types, or even a tuple pair of
260+
/// `Bound` start and end values. To drain the map entirely, use `RangeFull`
261+
/// like `map.drain(..)`.
262+
///
263+
/// This shifts down all entries following the drained range to fill the
264+
/// gap, and keeps the allocated memory for reuse.
265+
///
266+
/// ***Panics*** if the starting point is greater than the end point or if
267+
/// the end point is greater than the length of the map.
268+
pub fn drain<R>(&mut self, range: R) -> Drain<'_, K, V>
269+
where
270+
R: RangeBounds<usize>,
271+
{
258272
Drain {
259273
iter: self.core.drain(range),
260274
}

src/map/core.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ use crate::vec::{Drain, Vec};
1515
use core::cmp;
1616
use core::fmt;
1717
use core::mem::replace;
18-
use core::ops::RangeFull;
18+
use core::ops::RangeBounds;
1919

2020
use crate::equivalent::Equivalent;
21-
use crate::util::enumerate;
21+
use crate::util::{enumerate, simplify_range};
2222
use crate::{Bucket, Entries, HashValue};
2323

2424
/// Core of the map that does not depend on S
@@ -129,8 +129,12 @@ impl<K, V> IndexMapCore<K, V> {
129129
self.entries.clear();
130130
}
131131

132-
pub(crate) fn drain(&mut self, range: RangeFull) -> Drain<'_, Bucket<K, V>> {
133-
self.indices.clear();
132+
pub(crate) fn drain<R>(&mut self, range: R) -> Drain<'_, Bucket<K, V>>
133+
where
134+
R: RangeBounds<usize>,
135+
{
136+
let range = simplify_range(range, self.entries.len());
137+
self.erase_indices(range.start, range.end);
134138
self.entries.drain(range)
135139
}
136140

src/map/core/raw.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//! mostly in dealing with its bucket "pointers".
44
55
use super::{Entry, Equivalent, HashValue, IndexMapCore, VacantEntry};
6+
use crate::util::enumerate;
67
use core::fmt;
78
use core::mem::replace;
89
use hashbrown::raw::RawTable;
@@ -44,11 +45,75 @@ impl<K, V> IndexMapCore<K, V> {
4445
}
4546
}
4647

48+
/// Erase the given index from `indices`.
49+
///
50+
/// The index doesn't need to be valid in `entries` while calling this. No other index
51+
/// adjustments are made -- this is only used by `pop` for the greatest index.
4752
pub(super) fn erase_index(&mut self, hash: HashValue, index: usize) {
53+
debug_assert_eq!(index, self.indices.len() - 1);
4854
let raw_bucket = self.find_index(hash, index).unwrap();
4955
unsafe { self.indices.erase(raw_bucket) };
5056
}
5157

58+
/// Erase `start..end` from `indices`, and shift `end..` indices down to `start..`
59+
///
60+
/// All of these items should still be at their original location in `entries`.
61+
/// This is used by `drain`, which will let `Vec::drain` do the work on `entries`.
62+
pub(super) fn erase_indices(&mut self, start: usize, end: usize) {
63+
let (init, shifted_entries) = self.entries.split_at(end);
64+
let (start_entries, erased_entries) = init.split_at(start);
65+
66+
let erased = erased_entries.len();
67+
let shifted = shifted_entries.len();
68+
let half_capacity = self.indices.buckets() / 2;
69+
70+
// Use a heuristic between different strategies
71+
if erased == 0 {
72+
// Degenerate case, nothing to do
73+
} else if start + shifted < half_capacity && start < erased {
74+
// Reinsert everything, as there are few kept indices
75+
self.indices.clear();
76+
77+
// Reinsert stable indices
78+
for (i, entry) in enumerate(start_entries) {
79+
self.indices.insert_no_grow(entry.hash.get(), i);
80+
}
81+
82+
// Reinsert shifted indices
83+
for (i, entry) in (start..).zip(shifted_entries) {
84+
self.indices.insert_no_grow(entry.hash.get(), i);
85+
}
86+
} else if erased + shifted < half_capacity {
87+
// Find each affected index, as there are few to adjust
88+
89+
// Find erased indices
90+
for (i, entry) in (start..).zip(erased_entries) {
91+
let bucket = self.find_index(entry.hash, i).unwrap();
92+
unsafe { self.indices.erase(bucket) };
93+
}
94+
95+
// Find shifted indices
96+
for ((new, old), entry) in (start..).zip(end..).zip(shifted_entries) {
97+
let bucket = self.find_index(entry.hash, old).unwrap();
98+
unsafe { bucket.write(new) };
99+
}
100+
} else {
101+
// Sweep the whole table for adjustments
102+
unsafe {
103+
for bucket in self.indices.iter() {
104+
let i = bucket.read();
105+
if i >= end {
106+
bucket.write(i - erased);
107+
} else if i >= start {
108+
self.indices.erase(bucket);
109+
}
110+
}
111+
}
112+
}
113+
114+
debug_assert_eq!(self.indices.len(), start + shifted);
115+
}
116+
52117
pub(crate) fn entry(&mut self, hash: HashValue, key: K) -> Entry<'_, K, V>
53118
where
54119
K: Eq,

src/set.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use core::cmp::Ordering;
1111
use core::fmt;
1212
use core::hash::{BuildHasher, Hash};
1313
use core::iter::{Chain, FromIterator};
14-
use core::ops::{BitAnd, BitOr, BitXor, RangeFull, Sub};
14+
use core::ops::{BitAnd, BitOr, BitXor, RangeBounds, Sub};
1515
use core::slice;
1616

1717
use super::{Entries, Equivalent, IndexMap};
@@ -200,9 +200,23 @@ impl<T, S> IndexSet<T, S> {
200200
self.map.clear();
201201
}
202202

203-
/// Clears the `IndexSet`, returning all values as a drain iterator.
204-
/// Keeps the allocated memory for reuse.
205-
pub fn drain(&mut self, range: RangeFull) -> Drain<'_, T> {
203+
/// Clears the `IndexSet` in the given index range, returning those values
204+
/// as a drain iterator.
205+
///
206+
/// The range may be any type that implements `RangeBounds<usize>`,
207+
/// including all of the `std::ops::Range*` types, or even a tuple pair of
208+
/// `Bound` start and end values. To drain the set entirely, use `RangeFull`
209+
/// like `set.drain(..)`.
210+
///
211+
/// This shifts down all entries following the drained range to fill the
212+
/// gap, and keeps the allocated memory for reuse.
213+
///
214+
/// ***Panics*** if the starting point is greater than the end point or if
215+
/// the end point is greater than the length of the set.
216+
pub fn drain<R>(&mut self, range: R) -> Drain<'_, T>
217+
where
218+
R: RangeBounds<usize>,
219+
{
206220
Drain {
207221
iter: self.map.drain(range).iter,
208222
}

src/util.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use core::iter::Enumerate;
2+
use core::ops::{Bound, Range, RangeBounds};
23

34
pub(crate) fn third<A, B, C>(t: (A, B, C)) -> C {
45
t.2
@@ -10,3 +11,29 @@ where
1011
{
1112
iterable.into_iter().enumerate()
1213
}
14+
15+
pub(crate) fn simplify_range<R>(range: R, len: usize) -> Range<usize>
16+
where
17+
R: RangeBounds<usize>,
18+
{
19+
let start = match range.start_bound() {
20+
Bound::Unbounded => 0,
21+
Bound::Included(&i) if i <= len => i,
22+
Bound::Excluded(&i) if i < len => i + 1,
23+
bound => panic!("range start {:?} should be <= length {}", bound, len),
24+
};
25+
let end = match range.end_bound() {
26+
Bound::Unbounded => len,
27+
Bound::Excluded(&i) if i <= len => i,
28+
Bound::Included(&i) if i < len => i + 1,
29+
bound => panic!("range end {:?} should be <= length {}", bound, len),
30+
};
31+
if start > end {
32+
panic!(
33+
"range start {:?} should be <= range end {:?}",
34+
range.start_bound(),
35+
range.end_bound()
36+
);
37+
}
38+
start..end
39+
}

tests/quick.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use itertools::Itertools;
44
use quickcheck::quickcheck;
55
use quickcheck::Arbitrary;
66
use quickcheck::Gen;
7+
use quickcheck::TestResult;
78

89
use rand::Rng;
910

@@ -18,6 +19,7 @@ use std::collections::HashSet;
1819
use std::fmt::Debug;
1920
use std::hash::Hash;
2021
use std::iter::FromIterator;
22+
use std::ops::Bound;
2123
use std::ops::Deref;
2224

2325
use indexmap::map::Entry as OEntry;
@@ -100,7 +102,7 @@ quickcheck! {
100102
map.capacity() >= cap
101103
}
102104

103-
fn drain(insert: Vec<u8>) -> bool {
105+
fn drain_full(insert: Vec<u8>) -> bool {
104106
let mut map = IndexMap::new();
105107
for &key in &insert {
106108
map.insert(key, ());
@@ -113,6 +115,32 @@ quickcheck! {
113115
map.is_empty()
114116
}
115117

118+
fn drain_bounds(insert: Vec<u8>, range: (Bound<usize>, Bound<usize>)) -> TestResult {
119+
let mut map = IndexMap::new();
120+
for &key in &insert {
121+
map.insert(key, ());
122+
}
123+
124+
// First see if `Vec::drain` is happy with this range.
125+
let result = std::panic::catch_unwind(|| {
126+
let mut keys: Vec<u8> = map.keys().cloned().collect();
127+
keys.drain(range);
128+
keys
129+
});
130+
131+
if let Ok(keys) = result {
132+
map.drain(range);
133+
// Check that our `drain` matches the same key order.
134+
assert!(map.keys().eq(&keys));
135+
// Check that hash lookups all work too.
136+
assert!(keys.iter().all(|key| map.contains_key(key)));
137+
TestResult::passed()
138+
} else {
139+
// If `Vec::drain` panicked, so should we.
140+
TestResult::must_fail(move || { map.drain(range); })
141+
}
142+
}
143+
116144
fn shift_remove(insert: Vec<u8>, remove: Vec<u8>) -> bool {
117145
let mut map = IndexMap::new();
118146
for &key in &insert {

0 commit comments

Comments
 (0)