Skip to content

Commit

Permalink
Merge pull request #340 from cuviper/insert-bounds
Browse files Browse the repository at this point in the history
Assert bounds in `shift_insert` and add `insert_before`
  • Loading branch information
cuviper committed Aug 30, 2024
2 parents 922c6ad + 1d9b5e3 commit 139d7ad
Show file tree
Hide file tree
Showing 3 changed files with 240 additions and 8 deletions.
120 changes: 117 additions & 3 deletions src/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ where
///
/// This is equivalent to finding the position with
/// [`binary_search_keys`][Self::binary_search_keys], then either updating
/// it or calling [`shift_insert`][Self::shift_insert] for a new key.
/// it or calling [`insert_before`][Self::insert_before] for a new key.
///
/// If the sorted key is found in the map, its corresponding value is
/// updated with `value`, and the older value is returned inside
Expand All @@ -441,33 +441,147 @@ where
{
match self.binary_search_keys(&key) {
Ok(i) => (i, Some(mem::replace(&mut self[i], value))),
Err(i) => (i, self.shift_insert(i, key, value)),
Err(i) => self.insert_before(i, key, value),
}
}

/// Insert a key-value pair in the map at the given index.
/// Insert a key-value pair in the map before the entry at the given index, or at the end.
///
/// If an equivalent key already exists in the map: the key remains and
/// is moved to the new position in the map, its corresponding value is updated
/// with `value`, and the older value is returned inside `Some(_)`. The returned index
/// will either be the given index or one less, depending on how the entry moved.
/// (See [`shift_insert`](Self::shift_insert) for different behavior here.)
///
/// If no equivalent key existed in the map: the new key-value pair is
/// inserted exactly at the given index, and `None` is returned.
///
/// ***Panics*** if `index` is out of bounds.
/// Valid indices are `0..=map.len()` (inclusive).
///
/// Computes in **O(n)** time (average).
///
/// See also [`entry`][Self::entry] if you want to insert *or* modify,
/// perhaps only using the index for new entries with [`VacantEntry::shift_insert`].
///
/// # Examples
///
/// ```
/// use indexmap::IndexMap;
/// let mut map: IndexMap<char, ()> = ('a'..='z').map(|c| (c, ())).collect();
///
/// // The new key '*' goes exactly at the given index.
/// assert_eq!(map.get_index_of(&'*'), None);
/// assert_eq!(map.insert_before(10, '*', ()), (10, None));
/// assert_eq!(map.get_index_of(&'*'), Some(10));
///
/// // Moving the key 'a' up will shift others down, so this moves *before* 10 to index 9.
/// assert_eq!(map.insert_before(10, 'a', ()), (9, Some(())));
/// assert_eq!(map.get_index_of(&'a'), Some(9));
/// assert_eq!(map.get_index_of(&'*'), Some(10));
///
/// // Moving the key 'z' down will shift others up, so this moves to exactly 10.
/// assert_eq!(map.insert_before(10, 'z', ()), (10, Some(())));
/// assert_eq!(map.get_index_of(&'z'), Some(10));
/// assert_eq!(map.get_index_of(&'*'), Some(11));
///
/// // Moving or inserting before the endpoint is also valid.
/// assert_eq!(map.len(), 27);
/// assert_eq!(map.insert_before(map.len(), '*', ()), (26, Some(())));
/// assert_eq!(map.get_index_of(&'*'), Some(26));
/// assert_eq!(map.insert_before(map.len(), '+', ()), (27, None));
/// assert_eq!(map.get_index_of(&'+'), Some(27));
/// assert_eq!(map.len(), 28);
/// ```
pub fn insert_before(&mut self, mut index: usize, key: K, value: V) -> (usize, Option<V>) {
assert!(index <= self.len(), "index out of bounds");
match self.entry(key) {
Entry::Occupied(mut entry) => {
if index > entry.index() {
// Some entries will shift down when this one moves up,
// so "insert before index" becomes "move to index - 1",
// keeping the entry at the original index unmoved.
index -= 1;
}
let old = mem::replace(entry.get_mut(), value);
entry.move_index(index);
(index, Some(old))
}
Entry::Vacant(entry) => {
entry.shift_insert(index, value);
(index, None)
}
}
}

/// Insert a key-value pair in the map at the given index.
///
/// If an equivalent key already exists in the map: the key remains and
/// is moved to the given index in the map, its corresponding value is updated
/// with `value`, and the older value is returned inside `Some(_)`.
/// Note that existing entries **cannot** be moved to `index == map.len()`!
/// (See [`insert_before`](Self::insert_before) for different behavior here.)
///
/// If no equivalent key existed in the map: the new key-value pair is
/// inserted at the given index, and `None` is returned.
///
/// ***Panics*** if `index` is out of bounds.
/// Valid indices are `0..map.len()` (exclusive) when moving an existing entry, or
/// `0..=map.len()` (inclusive) when inserting a new key.
///
/// Computes in **O(n)** time (average).
///
/// See also [`entry`][Self::entry] if you want to insert *or* modify,
/// perhaps only using the index for new entries with [`VacantEntry::shift_insert`].
///
/// # Examples
///
/// ```
/// use indexmap::IndexMap;
/// let mut map: IndexMap<char, ()> = ('a'..='z').map(|c| (c, ())).collect();
///
/// // The new key '*' goes exactly at the given index.
/// assert_eq!(map.get_index_of(&'*'), None);
/// assert_eq!(map.shift_insert(10, '*', ()), None);
/// assert_eq!(map.get_index_of(&'*'), Some(10));
///
/// // Moving the key 'a' up to 10 will shift others down, including the '*' that was at 10.
/// assert_eq!(map.shift_insert(10, 'a', ()), Some(()));
/// assert_eq!(map.get_index_of(&'a'), Some(10));
/// assert_eq!(map.get_index_of(&'*'), Some(9));
///
/// // Moving the key 'z' down to 9 will shift others up, including the '*' that was at 9.
/// assert_eq!(map.shift_insert(9, 'z', ()), Some(()));
/// assert_eq!(map.get_index_of(&'z'), Some(9));
/// assert_eq!(map.get_index_of(&'*'), Some(10));
///
/// // Existing keys can move to len-1 at most, but new keys can insert at the endpoint.
/// assert_eq!(map.len(), 27);
/// assert_eq!(map.shift_insert(map.len() - 1, '*', ()), Some(()));
/// assert_eq!(map.get_index_of(&'*'), Some(26));
/// assert_eq!(map.shift_insert(map.len(), '+', ()), None);
/// assert_eq!(map.get_index_of(&'+'), Some(27));
/// assert_eq!(map.len(), 28);
/// ```
///
/// ```should_panic
/// use indexmap::IndexMap;
/// let mut map: IndexMap<char, ()> = ('a'..='z').map(|c| (c, ())).collect();
///
/// // This is an invalid index for moving an existing key!
/// map.shift_insert(map.len(), 'a', ());
/// ```
pub fn shift_insert(&mut self, index: usize, key: K, value: V) -> Option<V> {
let len = self.len();
match self.entry(key) {
Entry::Occupied(mut entry) => {
assert!(index < len, "index out of bounds");
let old = mem::replace(entry.get_mut(), value);
entry.move_index(index);
Some(old)
}
Entry::Vacant(entry) => {
assert!(index <= len, "index out of bounds");
entry.shift_insert(index, value);
None
}
Expand Down
28 changes: 28 additions & 0 deletions src/map/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,34 @@ fn shift_insert() {
}
}

#[test]
fn insert_sorted_bad() {
let mut map = IndexMap::new();
map.insert(10, ());
for i in 0..10 {
map.insert(i, ());
}

// The binary search will want to insert this at the end (index == len()),
// but that's only possible for *new* inserts. It should still be handled
// without panicking though, and in this case it's simple enough that we
// know the exact result. (But don't read this as an API guarantee!)
assert_eq!(map.first(), Some((&10, &())));
map.insert_sorted(10, ());
assert_eq!(map.last(), Some((&10, &())));
assert!(map.keys().copied().eq(0..=10));

// Other out-of-order entries can also "insert" to a binary-searched
// position, moving in either direction.
map.move_index(5, 0);
map.move_index(6, 10);
assert_eq!(map.first(), Some((&5, &())));
assert_eq!(map.last(), Some((&6, &())));
map.insert_sorted(5, ()); // moves back up
map.insert_sorted(6, ()); // moves back down
assert!(map.keys().copied().eq(0..=10));
}

#[test]
fn grow() {
let insert = [0, 4, 2, 12, 8, 7, 11];
Expand Down
100 changes: 95 additions & 5 deletions src/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ where
///
/// This is equivalent to finding the position with
/// [`binary_search`][Self::binary_search], and if needed calling
/// [`shift_insert`][Self::shift_insert] for a new value.
/// [`insert_before`][Self::insert_before] for a new value.
///
/// If the sorted item is found in the set, it returns the index of that
/// existing item and `false`, without any change. Otherwise, it inserts the
Expand All @@ -383,16 +383,106 @@ where
(index, existing.is_none())
}

/// Insert the value into the set before the value at the given index, or at the end.
///
/// If an equivalent item already exists in the set, it returns `false` leaving the
/// original value in the set, but moved to the new position. The returned index
/// will either be the given index or one less, depending on how the value moved.
/// (See [`shift_insert`](Self::shift_insert) for different behavior here.)
///
/// Otherwise, it inserts the new value exactly at the given index and returns `true`.
///
/// ***Panics*** if `index` is out of bounds.
/// Valid indices are `0..=set.len()` (inclusive).
///
/// Computes in **O(n)** time (average).
///
/// # Examples
///
/// ```
/// use indexmap::IndexSet;
/// let mut set: IndexSet<char> = ('a'..='z').collect();
///
/// // The new value '*' goes exactly at the given index.
/// assert_eq!(set.get_index_of(&'*'), None);
/// assert_eq!(set.insert_before(10, '*'), (10, true));
/// assert_eq!(set.get_index_of(&'*'), Some(10));
///
/// // Moving the value 'a' up will shift others down, so this moves *before* 10 to index 9.
/// assert_eq!(set.insert_before(10, 'a'), (9, false));
/// assert_eq!(set.get_index_of(&'a'), Some(9));
/// assert_eq!(set.get_index_of(&'*'), Some(10));
///
/// // Moving the value 'z' down will shift others up, so this moves to exactly 10.
/// assert_eq!(set.insert_before(10, 'z'), (10, false));
/// assert_eq!(set.get_index_of(&'z'), Some(10));
/// assert_eq!(set.get_index_of(&'*'), Some(11));
///
/// // Moving or inserting before the endpoint is also valid.
/// assert_eq!(set.len(), 27);
/// assert_eq!(set.insert_before(set.len(), '*'), (26, false));
/// assert_eq!(set.get_index_of(&'*'), Some(26));
/// assert_eq!(set.insert_before(set.len(), '+'), (27, true));
/// assert_eq!(set.get_index_of(&'+'), Some(27));
/// assert_eq!(set.len(), 28);
/// ```
pub fn insert_before(&mut self, index: usize, value: T) -> (usize, bool) {
let (index, existing) = self.map.insert_before(index, value, ());
(index, existing.is_none())
}

/// Insert the value into the set at the given index.
///
/// If an equivalent item already exists in the set, it returns
/// `false` leaving the original value in the set, but moving it to
/// the new position in the set. Otherwise, it inserts the new
/// item at the given index and returns `true`.
/// If an equivalent item already exists in the set, it returns `false` leaving
/// the original value in the set, but moved to the given index.
/// Note that existing values **cannot** be moved to `index == set.len()`!
/// (See [`insert_before`](Self::insert_before) for different behavior here.)
///
/// Otherwise, it inserts the new value at the given index and returns `true`.
///
/// ***Panics*** if `index` is out of bounds.
/// Valid indices are `0..set.len()` (exclusive) when moving an existing value, or
/// `0..=set.len()` (inclusive) when inserting a new value.
///
/// Computes in **O(n)** time (average).
///
/// # Examples
///
/// ```
/// use indexmap::IndexSet;
/// let mut set: IndexSet<char> = ('a'..='z').collect();
///
/// // The new value '*' goes exactly at the given index.
/// assert_eq!(set.get_index_of(&'*'), None);
/// assert_eq!(set.shift_insert(10, '*'), true);
/// assert_eq!(set.get_index_of(&'*'), Some(10));
///
/// // Moving the value 'a' up to 10 will shift others down, including the '*' that was at 10.
/// assert_eq!(set.shift_insert(10, 'a'), false);
/// assert_eq!(set.get_index_of(&'a'), Some(10));
/// assert_eq!(set.get_index_of(&'*'), Some(9));
///
/// // Moving the value 'z' down to 9 will shift others up, including the '*' that was at 9.
/// assert_eq!(set.shift_insert(9, 'z'), false);
/// assert_eq!(set.get_index_of(&'z'), Some(9));
/// assert_eq!(set.get_index_of(&'*'), Some(10));
///
/// // Existing values can move to len-1 at most, but new values can insert at the endpoint.
/// assert_eq!(set.len(), 27);
/// assert_eq!(set.shift_insert(set.len() - 1, '*'), false);
/// assert_eq!(set.get_index_of(&'*'), Some(26));
/// assert_eq!(set.shift_insert(set.len(), '+'), true);
/// assert_eq!(set.get_index_of(&'+'), Some(27));
/// assert_eq!(set.len(), 28);
/// ```
///
/// ```should_panic
/// use indexmap::IndexSet;
/// let mut set: IndexSet<char> = ('a'..='z').collect();
///
/// // This is an invalid index for moving an existing value!
/// set.shift_insert(set.len(), 'a');
/// ```
pub fn shift_insert(&mut self, index: usize, value: T) -> bool {
self.map.shift_insert(index, value, ()).is_none()
}
Expand Down

0 comments on commit 139d7ad

Please sign in to comment.