Add move_index to change the position of an entry

This moves the position of a key-value pair from one index to another by
shifting all other pairs in-between, making this an O(n) operation.

This could be used as a building-block for other operations, like #173
which wants to insert at a particular index. You can `insert_full` to
insert it _somewhere_, then choose whether to `move_index` depending on
whether you want to also change pre-existing entries.

(cherry picked from commit 54a48d2dd1647befc760ab05d807639f79375d97)
This commit is contained in:
Josh Stone
2021-02-27 11:36:53 -08:00
parent d92445b322
commit d1f3ef6c02
4 changed files with 126 additions and 13 deletions
+13
View File
@@ -845,6 +845,19 @@ impl<K, V, S> IndexMap<K, V, S> {
self.core.shift_remove_index(index)
}
/// Moves the position of a key-value pair from one index to another
/// by shifting all other pairs in-between.
///
/// * If `from < to`, the other pairs will shift down while the targeted pair moves up.
/// * If `from > to`, the other pairs will shift up while the targeted pair moves down.
///
/// ***Panics*** if `from` or `to` are out of bounds.
///
/// Computes in **O(n)** time (average).
pub fn move_index(&mut self, from: usize, to: usize) {
self.core.move_index(from, to)
}
/// Swaps the position of two key-value pairs in the map.
///
/// ***Panics*** if `a` or `b` are out of bounds.
+61 -13
View File
@@ -283,29 +283,77 @@ impl<K, V> IndexMapCore<K, V> {
///
/// The index should already be removed from `self.indices`.
fn shift_remove_finish(&mut self, index: usize) -> (K, V) {
// use Vec::remove, but then we need to update the indices that point
// to all of the other entries that have to move
let entry = self.entries.remove(index);
// Correct indices that point to the entries that followed the removed entry.
self.decrement_indices(index + 1, self.entries.len());
// correct indices that point to the entries that followed the removed entry.
// use a heuristic between a full sweep vs. a `find()` for every shifted item.
let raw_capacity = self.indices.buckets();
let shifted_entries = &self.entries[index..];
if shifted_entries.len() > raw_capacity / 2 {
// shift all indices greater than `index`
// Use Vec::remove to actually remove the entry.
let entry = self.entries.remove(index);
(entry.key, entry.value)
}
/// Decrement all indices in the range `start..end`.
///
/// The index `start - 1` should not exist in `self.indices`.
/// All entries should still be in their original positions.
fn decrement_indices(&mut self, start: usize, end: usize) {
// Use a heuristic between a full sweep vs. a `find()` for every shifted item.
let shifted_entries = &self.entries[start..end];
if shifted_entries.len() > self.indices.buckets() / 2 {
// Shift all indices in range.
for i in self.indices_mut() {
if *i > index {
if start <= *i && *i < end {
*i -= 1;
}
}
} else {
// find each following entry to shift its index
for (i, entry) in (index + 1..).zip(shifted_entries) {
// Find each entry in range to shift its index.
for (i, entry) in (start..end).zip(shifted_entries) {
update_index(&mut self.indices, entry.hash, i, i - 1);
}
}
}
(entry.key, entry.value)
/// Increment all indices in the range `start..end`.
///
/// The index `end` should not exist in `self.indices`.
/// All entries should still be in their original positions.
fn increment_indices(&mut self, start: usize, end: usize) {
// Use a heuristic between a full sweep vs. a `find()` for every shifted item.
let shifted_entries = &self.entries[start..end];
if shifted_entries.len() > self.indices.buckets() / 2 {
// Shift all indices in range.
for i in self.indices_mut() {
if start <= *i && *i < end {
*i += 1;
}
}
} else {
// Find each entry in range to shift its index, updated in reverse so
// we never have duplicated indices that might have a hash collision.
for (i, entry) in (start..end).zip(shifted_entries).rev() {
update_index(&mut self.indices, entry.hash, i, i + 1);
}
}
}
pub(super) fn move_index(&mut self, from: usize, to: usize) {
let from_hash = self.entries[from].hash;
if from != to {
// Use a sentinal index so other indices don't collide.
update_index(&mut self.indices, from_hash, from, usize::MAX);
// Update all other indices and rotate the entry positions.
if from < to {
self.decrement_indices(from + 1, to + 1);
self.entries[from..=to].rotate_left(1);
} else if to < from {
self.increment_indices(to, from);
self.entries[to..=from].rotate_right(1);
}
// Change the sentinal index to its final position.
update_index(&mut self.indices, from_hash, usize::MAX, to);
}
}
/// Remove an entry by swapping it with the last
+13
View File
@@ -700,6 +700,19 @@ impl<T, S> IndexSet<T, S> {
self.map.shift_remove_index(index).map(|(x, ())| x)
}
/// Moves the position of a value from one index to another
/// by shifting all other values in-between.
///
/// * If `from < to`, the other values will shift down while the targeted value moves up.
/// * If `from > to`, the other values will shift up while the targeted value moves down.
///
/// ***Panics*** if `from` or `to` are out of bounds.
///
/// Computes in **O(n)** time (average).
pub fn move_index(&mut self, from: usize, to: usize) {
self.map.move_index(from, to)
}
/// Swaps the position of two values in the set.
///
/// ***Panics*** if `a` or `b` are out of bounds.
+39
View File
@@ -216,6 +216,45 @@ quickcheck_limit! {
map[&key] == value && map[i] == value
})
}
fn swap_indices(vec: Vec<u8>, a: usize, b: usize) -> TestResult {
let mut set = IndexSet::<u8>::from_iter(vec);
if a >= set.len() || b >= set.len() {
return TestResult::discard();
}
let mut vec = Vec::from_iter(set.iter().cloned());
vec.swap(a, b);
set.swap_indices(a, b);
// Check both iteration order and hash lookups
assert!(set.iter().eq(vec.iter()));
assert!(vec.iter().enumerate().all(|(i, x)| {
set.get_index_of(x) == Some(i)
}));
TestResult::passed()
}
fn move_index(vec: Vec<u8>, from: usize, to: usize) -> TestResult {
let mut set = IndexSet::<u8>::from_iter(vec);
if from >= set.len() || to >= set.len() {
return TestResult::discard();
}
let mut vec = Vec::from_iter(set.iter().cloned());
let x = vec.remove(from);
vec.insert(to, x);
set.move_index(from, to);
// Check both iteration order and hash lookups
assert!(set.iter().eq(vec.iter()));
assert!(vec.iter().enumerate().all(|(i, x)| {
set.get_index_of(x) == Some(i)
}));
TestResult::passed()
}
}
use crate::Op::*;