Skip to content

Commit 139d7ad

Browse files
authored
Merge pull request #340 from cuviper/insert-bounds
Assert bounds in `shift_insert` and add `insert_before`
2 parents 922c6ad + 1d9b5e3 commit 139d7ad

File tree

3 files changed

+240
-8
lines changed

3 files changed

+240
-8
lines changed

src/map.rs

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ where
420420
///
421421
/// This is equivalent to finding the position with
422422
/// [`binary_search_keys`][Self::binary_search_keys], then either updating
423-
/// it or calling [`shift_insert`][Self::shift_insert] for a new key.
423+
/// it or calling [`insert_before`][Self::insert_before] for a new key.
424424
///
425425
/// If the sorted key is found in the map, its corresponding value is
426426
/// updated with `value`, and the older value is returned inside
@@ -441,33 +441,147 @@ where
441441
{
442442
match self.binary_search_keys(&key) {
443443
Ok(i) => (i, Some(mem::replace(&mut self[i], value))),
444-
Err(i) => (i, self.shift_insert(i, key, value)),
444+
Err(i) => self.insert_before(i, key, value),
445445
}
446446
}
447447

448-
/// Insert a key-value pair in the map at the given index.
448+
/// Insert a key-value pair in the map before the entry at the given index, or at the end.
449449
///
450450
/// If an equivalent key already exists in the map: the key remains and
451451
/// is moved to the new position in the map, its corresponding value is updated
452+
/// with `value`, and the older value is returned inside `Some(_)`. The returned index
453+
/// will either be the given index or one less, depending on how the entry moved.
454+
/// (See [`shift_insert`](Self::shift_insert) for different behavior here.)
455+
///
456+
/// If no equivalent key existed in the map: the new key-value pair is
457+
/// inserted exactly at the given index, and `None` is returned.
458+
///
459+
/// ***Panics*** if `index` is out of bounds.
460+
/// Valid indices are `0..=map.len()` (inclusive).
461+
///
462+
/// Computes in **O(n)** time (average).
463+
///
464+
/// See also [`entry`][Self::entry] if you want to insert *or* modify,
465+
/// perhaps only using the index for new entries with [`VacantEntry::shift_insert`].
466+
///
467+
/// # Examples
468+
///
469+
/// ```
470+
/// use indexmap::IndexMap;
471+
/// let mut map: IndexMap<char, ()> = ('a'..='z').map(|c| (c, ())).collect();
472+
///
473+
/// // The new key '*' goes exactly at the given index.
474+
/// assert_eq!(map.get_index_of(&'*'), None);
475+
/// assert_eq!(map.insert_before(10, '*', ()), (10, None));
476+
/// assert_eq!(map.get_index_of(&'*'), Some(10));
477+
///
478+
/// // Moving the key 'a' up will shift others down, so this moves *before* 10 to index 9.
479+
/// assert_eq!(map.insert_before(10, 'a', ()), (9, Some(())));
480+
/// assert_eq!(map.get_index_of(&'a'), Some(9));
481+
/// assert_eq!(map.get_index_of(&'*'), Some(10));
482+
///
483+
/// // Moving the key 'z' down will shift others up, so this moves to exactly 10.
484+
/// assert_eq!(map.insert_before(10, 'z', ()), (10, Some(())));
485+
/// assert_eq!(map.get_index_of(&'z'), Some(10));
486+
/// assert_eq!(map.get_index_of(&'*'), Some(11));
487+
///
488+
/// // Moving or inserting before the endpoint is also valid.
489+
/// assert_eq!(map.len(), 27);
490+
/// assert_eq!(map.insert_before(map.len(), '*', ()), (26, Some(())));
491+
/// assert_eq!(map.get_index_of(&'*'), Some(26));
492+
/// assert_eq!(map.insert_before(map.len(), '+', ()), (27, None));
493+
/// assert_eq!(map.get_index_of(&'+'), Some(27));
494+
/// assert_eq!(map.len(), 28);
495+
/// ```
496+
pub fn insert_before(&mut self, mut index: usize, key: K, value: V) -> (usize, Option<V>) {
497+
assert!(index <= self.len(), "index out of bounds");
498+
match self.entry(key) {
499+
Entry::Occupied(mut entry) => {
500+
if index > entry.index() {
501+
// Some entries will shift down when this one moves up,
502+
// so "insert before index" becomes "move to index - 1",
503+
// keeping the entry at the original index unmoved.
504+
index -= 1;
505+
}
506+
let old = mem::replace(entry.get_mut(), value);
507+
entry.move_index(index);
508+
(index, Some(old))
509+
}
510+
Entry::Vacant(entry) => {
511+
entry.shift_insert(index, value);
512+
(index, None)
513+
}
514+
}
515+
}
516+
517+
/// Insert a key-value pair in the map at the given index.
518+
///
519+
/// If an equivalent key already exists in the map: the key remains and
520+
/// is moved to the given index in the map, its corresponding value is updated
452521
/// with `value`, and the older value is returned inside `Some(_)`.
522+
/// Note that existing entries **cannot** be moved to `index == map.len()`!
523+
/// (See [`insert_before`](Self::insert_before) for different behavior here.)
453524
///
454525
/// If no equivalent key existed in the map: the new key-value pair is
455526
/// inserted at the given index, and `None` is returned.
456527
///
457528
/// ***Panics*** if `index` is out of bounds.
529+
/// Valid indices are `0..map.len()` (exclusive) when moving an existing entry, or
530+
/// `0..=map.len()` (inclusive) when inserting a new key.
458531
///
459532
/// Computes in **O(n)** time (average).
460533
///
461534
/// See also [`entry`][Self::entry] if you want to insert *or* modify,
462535
/// perhaps only using the index for new entries with [`VacantEntry::shift_insert`].
536+
///
537+
/// # Examples
538+
///
539+
/// ```
540+
/// use indexmap::IndexMap;
541+
/// let mut map: IndexMap<char, ()> = ('a'..='z').map(|c| (c, ())).collect();
542+
///
543+
/// // The new key '*' goes exactly at the given index.
544+
/// assert_eq!(map.get_index_of(&'*'), None);
545+
/// assert_eq!(map.shift_insert(10, '*', ()), None);
546+
/// assert_eq!(map.get_index_of(&'*'), Some(10));
547+
///
548+
/// // Moving the key 'a' up to 10 will shift others down, including the '*' that was at 10.
549+
/// assert_eq!(map.shift_insert(10, 'a', ()), Some(()));
550+
/// assert_eq!(map.get_index_of(&'a'), Some(10));
551+
/// assert_eq!(map.get_index_of(&'*'), Some(9));
552+
///
553+
/// // Moving the key 'z' down to 9 will shift others up, including the '*' that was at 9.
554+
/// assert_eq!(map.shift_insert(9, 'z', ()), Some(()));
555+
/// assert_eq!(map.get_index_of(&'z'), Some(9));
556+
/// assert_eq!(map.get_index_of(&'*'), Some(10));
557+
///
558+
/// // Existing keys can move to len-1 at most, but new keys can insert at the endpoint.
559+
/// assert_eq!(map.len(), 27);
560+
/// assert_eq!(map.shift_insert(map.len() - 1, '*', ()), Some(()));
561+
/// assert_eq!(map.get_index_of(&'*'), Some(26));
562+
/// assert_eq!(map.shift_insert(map.len(), '+', ()), None);
563+
/// assert_eq!(map.get_index_of(&'+'), Some(27));
564+
/// assert_eq!(map.len(), 28);
565+
/// ```
566+
///
567+
/// ```should_panic
568+
/// use indexmap::IndexMap;
569+
/// let mut map: IndexMap<char, ()> = ('a'..='z').map(|c| (c, ())).collect();
570+
///
571+
/// // This is an invalid index for moving an existing key!
572+
/// map.shift_insert(map.len(), 'a', ());
573+
/// ```
463574
pub fn shift_insert(&mut self, index: usize, key: K, value: V) -> Option<V> {
575+
let len = self.len();
464576
match self.entry(key) {
465577
Entry::Occupied(mut entry) => {
578+
assert!(index < len, "index out of bounds");
466579
let old = mem::replace(entry.get_mut(), value);
467580
entry.move_index(index);
468581
Some(old)
469582
}
470583
Entry::Vacant(entry) => {
584+
assert!(index <= len, "index out of bounds");
471585
entry.shift_insert(index, value);
472586
None
473587
}

src/map/tests.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,34 @@ fn shift_insert() {
135135
}
136136
}
137137

138+
#[test]
139+
fn insert_sorted_bad() {
140+
let mut map = IndexMap::new();
141+
map.insert(10, ());
142+
for i in 0..10 {
143+
map.insert(i, ());
144+
}
145+
146+
// The binary search will want to insert this at the end (index == len()),
147+
// but that's only possible for *new* inserts. It should still be handled
148+
// without panicking though, and in this case it's simple enough that we
149+
// know the exact result. (But don't read this as an API guarantee!)
150+
assert_eq!(map.first(), Some((&10, &())));
151+
map.insert_sorted(10, ());
152+
assert_eq!(map.last(), Some((&10, &())));
153+
assert!(map.keys().copied().eq(0..=10));
154+
155+
// Other out-of-order entries can also "insert" to a binary-searched
156+
// position, moving in either direction.
157+
map.move_index(5, 0);
158+
map.move_index(6, 10);
159+
assert_eq!(map.first(), Some((&5, &())));
160+
assert_eq!(map.last(), Some((&6, &())));
161+
map.insert_sorted(5, ()); // moves back up
162+
map.insert_sorted(6, ()); // moves back down
163+
assert!(map.keys().copied().eq(0..=10));
164+
}
165+
138166
#[test]
139167
fn grow() {
140168
let insert = [0, 4, 2, 12, 8, 7, 11];

src/set.rs

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ where
361361
///
362362
/// This is equivalent to finding the position with
363363
/// [`binary_search`][Self::binary_search], and if needed calling
364-
/// [`shift_insert`][Self::shift_insert] for a new value.
364+
/// [`insert_before`][Self::insert_before] for a new value.
365365
///
366366
/// If the sorted item is found in the set, it returns the index of that
367367
/// existing item and `false`, without any change. Otherwise, it inserts the
@@ -383,16 +383,106 @@ where
383383
(index, existing.is_none())
384384
}
385385

386+
/// Insert the value into the set before the value at the given index, or at the end.
387+
///
388+
/// If an equivalent item already exists in the set, it returns `false` leaving the
389+
/// original value in the set, but moved to the new position. The returned index
390+
/// will either be the given index or one less, depending on how the value moved.
391+
/// (See [`shift_insert`](Self::shift_insert) for different behavior here.)
392+
///
393+
/// Otherwise, it inserts the new value exactly at the given index and returns `true`.
394+
///
395+
/// ***Panics*** if `index` is out of bounds.
396+
/// Valid indices are `0..=set.len()` (inclusive).
397+
///
398+
/// Computes in **O(n)** time (average).
399+
///
400+
/// # Examples
401+
///
402+
/// ```
403+
/// use indexmap::IndexSet;
404+
/// let mut set: IndexSet<char> = ('a'..='z').collect();
405+
///
406+
/// // The new value '*' goes exactly at the given index.
407+
/// assert_eq!(set.get_index_of(&'*'), None);
408+
/// assert_eq!(set.insert_before(10, '*'), (10, true));
409+
/// assert_eq!(set.get_index_of(&'*'), Some(10));
410+
///
411+
/// // Moving the value 'a' up will shift others down, so this moves *before* 10 to index 9.
412+
/// assert_eq!(set.insert_before(10, 'a'), (9, false));
413+
/// assert_eq!(set.get_index_of(&'a'), Some(9));
414+
/// assert_eq!(set.get_index_of(&'*'), Some(10));
415+
///
416+
/// // Moving the value 'z' down will shift others up, so this moves to exactly 10.
417+
/// assert_eq!(set.insert_before(10, 'z'), (10, false));
418+
/// assert_eq!(set.get_index_of(&'z'), Some(10));
419+
/// assert_eq!(set.get_index_of(&'*'), Some(11));
420+
///
421+
/// // Moving or inserting before the endpoint is also valid.
422+
/// assert_eq!(set.len(), 27);
423+
/// assert_eq!(set.insert_before(set.len(), '*'), (26, false));
424+
/// assert_eq!(set.get_index_of(&'*'), Some(26));
425+
/// assert_eq!(set.insert_before(set.len(), '+'), (27, true));
426+
/// assert_eq!(set.get_index_of(&'+'), Some(27));
427+
/// assert_eq!(set.len(), 28);
428+
/// ```
429+
pub fn insert_before(&mut self, index: usize, value: T) -> (usize, bool) {
430+
let (index, existing) = self.map.insert_before(index, value, ());
431+
(index, existing.is_none())
432+
}
433+
386434
/// Insert the value into the set at the given index.
387435
///
388-
/// If an equivalent item already exists in the set, it returns
389-
/// `false` leaving the original value in the set, but moving it to
390-
/// the new position in the set. Otherwise, it inserts the new
391-
/// item at the given index and returns `true`.
436+
/// If an equivalent item already exists in the set, it returns `false` leaving
437+
/// the original value in the set, but moved to the given index.
438+
/// Note that existing values **cannot** be moved to `index == set.len()`!
439+
/// (See [`insert_before`](Self::insert_before) for different behavior here.)
440+
///
441+
/// Otherwise, it inserts the new value at the given index and returns `true`.
392442
///
393443
/// ***Panics*** if `index` is out of bounds.
444+
/// Valid indices are `0..set.len()` (exclusive) when moving an existing value, or
445+
/// `0..=set.len()` (inclusive) when inserting a new value.
394446
///
395447
/// Computes in **O(n)** time (average).
448+
///
449+
/// # Examples
450+
///
451+
/// ```
452+
/// use indexmap::IndexSet;
453+
/// let mut set: IndexSet<char> = ('a'..='z').collect();
454+
///
455+
/// // The new value '*' goes exactly at the given index.
456+
/// assert_eq!(set.get_index_of(&'*'), None);
457+
/// assert_eq!(set.shift_insert(10, '*'), true);
458+
/// assert_eq!(set.get_index_of(&'*'), Some(10));
459+
///
460+
/// // Moving the value 'a' up to 10 will shift others down, including the '*' that was at 10.
461+
/// assert_eq!(set.shift_insert(10, 'a'), false);
462+
/// assert_eq!(set.get_index_of(&'a'), Some(10));
463+
/// assert_eq!(set.get_index_of(&'*'), Some(9));
464+
///
465+
/// // Moving the value 'z' down to 9 will shift others up, including the '*' that was at 9.
466+
/// assert_eq!(set.shift_insert(9, 'z'), false);
467+
/// assert_eq!(set.get_index_of(&'z'), Some(9));
468+
/// assert_eq!(set.get_index_of(&'*'), Some(10));
469+
///
470+
/// // Existing values can move to len-1 at most, but new values can insert at the endpoint.
471+
/// assert_eq!(set.len(), 27);
472+
/// assert_eq!(set.shift_insert(set.len() - 1, '*'), false);
473+
/// assert_eq!(set.get_index_of(&'*'), Some(26));
474+
/// assert_eq!(set.shift_insert(set.len(), '+'), true);
475+
/// assert_eq!(set.get_index_of(&'+'), Some(27));
476+
/// assert_eq!(set.len(), 28);
477+
/// ```
478+
///
479+
/// ```should_panic
480+
/// use indexmap::IndexSet;
481+
/// let mut set: IndexSet<char> = ('a'..='z').collect();
482+
///
483+
/// // This is an invalid index for moving an existing value!
484+
/// set.shift_insert(set.len(), 'a');
485+
/// ```
396486
pub fn shift_insert(&mut self, index: usize, value: T) -> bool {
397487
self.map.shift_insert(index, value, ()).is_none()
398488
}

0 commit comments

Comments
 (0)