From 51907787ee69bfaf54aee393d488276ad4461125 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sat, 10 Feb 2024 10:42:23 -0800 Subject: [PATCH 1/8] Add more indexed methods to entries For `IndexedEntry`, `OccupiedEntry`, and `RawOccupiedEntryMut`, this adds `move_index` and `swap_indices` methods that work like the top-level `IndexMap` methods using the current index of the entry. For `VacantEntry` this adds `shift_insert`, while `RawVacantEntryMut` adds `shift_insert` and `shift_insert_hashed_nocheck`, offering a way to insert at a particular index while shifting other entries as needed. --- src/map.rs | 2 ++ src/map/core.rs | 34 ++++++++++++++++++ src/map/core/entry.rs | 65 +++++++++++++++++++++++++++++++--- src/map/core/raw.rs | 6 ++++ src/map/core/raw_entry_v1.rs | 68 ++++++++++++++++++++++++++++++++---- 5 files changed, 163 insertions(+), 12 deletions(-) diff --git a/src/map.rs b/src/map.rs index 428fac40..93cd82f3 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1051,6 +1051,8 @@ impl IndexMap { /// Swaps the position of two key-value pairs in the map. /// /// ***Panics*** if `a` or `b` are out of bounds. + /// + /// Computes in **O(1)** time (average). pub fn swap_indices(&mut self, a: usize, b: usize) { self.core.swap_indices(a, b) } diff --git a/src/map/core.rs b/src/map/core.rs index 2dca04a5..749714f1 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -311,6 +311,17 @@ impl IndexMapCore { self.entries.push(Bucket { hash, key, value }); } + /// Insert a key-value pair in `entries` at a particular index, + /// *without* checking whether it already exists. + fn insert_entry(&mut self, index: usize, hash: HashValue, key: K, value: V) { + if self.entries.len() == self.entries.capacity() { + // Reserve our own capacity synced to the indices, + // rather than letting `Vec::insert` just double it. + self.reserve_entries(1); + } + self.entries.insert(index, Bucket { hash, key, value }); + } + /// Return the index in `entries` where an equivalent key can be found pub(crate) fn get_index_of(&self, hash: HashValue, key: &Q) -> Option where @@ -361,6 +372,29 @@ impl IndexMapCore { } } + fn insert_unique(&mut self, hash: HashValue, key: K, value: V) -> usize { + let i = self.indices.len(); + self.indices.insert(hash.get(), i, get_hash(&self.entries)); + debug_assert_eq!(i, self.entries.len()); + self.push_entry(hash, key, value); + i + } + + fn shift_insert_unique(&mut self, index: usize, hash: HashValue, key: K, value: V) { + let end = self.indices.len(); + assert!(index <= end); + // Increment others first so we don't have duplicate indices. + self.increment_indices(index, end); + let entries = &*self.entries; + self.indices.insert(hash.get(), index, move |&i| { + // Adjust for the incremented indices to find hashes. + debug_assert_ne!(i, index); + let i = if i < index { i } else { i - 1 }; + entries[i].hash.get() + }); + self.insert_entry(index, hash, key, value); + } + /// Remove an entry by shifting all entries that follow it pub(crate) fn shift_remove_full(&mut self, hash: HashValue, key: &Q) -> Option<(usize, K, V)> where diff --git a/src/map/core/entry.rs b/src/map/core/entry.rs index ada26ef7..b329179d 100644 --- a/src/map/core/entry.rs +++ b/src/map/core/entry.rs @@ -1,5 +1,5 @@ use super::raw::RawTableEntry; -use super::{get_hash, IndexMapCore}; +use super::IndexMapCore; use crate::HashValue; use core::{fmt, mem}; @@ -237,6 +237,30 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { let (map, index) = self.raw.remove_index(); map.shift_remove_finish(index) } + + /// Moves the position of the entry to a new index + /// by shifting all other entries in-between. + /// + /// * If `self.index() < to`, the other pairs will shift down while the targeted pair moves up. + /// * If `self.index() > to`, the other pairs will shift up while the targeted pair moves down. + /// + /// ***Panics*** if `to` is out of bounds. + /// + /// Computes in **O(n)** time (average). + pub fn move_index(self, to: usize) { + let (map, index) = self.raw.into_inner(); + map.move_index(index, to); + } + + /// Swaps the position of entry with another. + /// + /// ***Panics*** if the `other` index is out of bounds. + /// + /// Computes in **O(1)** time (average). + pub fn swap_indices(self, other: usize) { + let (map, index) = self.raw.into_inner(); + map.swap_indices(index, other) + } } impl fmt::Debug for OccupiedEntry<'_, K, V> { @@ -275,13 +299,22 @@ impl<'a, K, V> VacantEntry<'a, K, V> { /// Inserts the entry's key and the given value into the map, and returns a mutable reference /// to the value. pub fn insert(self, value: V) -> &'a mut V { - let i = self.index(); let Self { map, hash, key } = self; - map.indices.insert(hash.get(), i, get_hash(&map.entries)); - debug_assert_eq!(i, map.entries.len()); - map.push_entry(hash, key, value); + let i = map.insert_unique(hash, key, value); &mut map.entries[i].value } + + /// Inserts the entry's key and the given value into the map at the given index, + /// shifting others to the right, and returns a mutable reference to the value. + /// + /// ***Panics*** if `index` is out of bounds. + /// + /// Computes in **O(n)** time (average). + pub fn shift_insert(self, index: usize, value: V) -> &'a mut V { + let Self { map, hash, key } = self; + map.shift_insert_unique(index, hash, key, value); + &mut map.entries[index].value + } } impl fmt::Debug for VacantEntry<'_, K, V> { @@ -383,6 +416,28 @@ impl<'a, K, V> IndexedEntry<'a, K, V> { pub fn shift_remove(self) -> V { self.shift_remove_entry().1 } + + /// Moves the position of the entry to a new index + /// by shifting all other entries in-between. + /// + /// * If `self.index() < to`, the other pairs will shift down while the targeted pair moves up. + /// * If `self.index() > to`, the other pairs will shift up while the targeted pair moves down. + /// + /// ***Panics*** if `to` is out of bounds. + /// + /// Computes in **O(n)** time (average). + pub fn move_index(self, to: usize) { + self.map.move_index(self.index, to); + } + + /// Swaps the position of entry with another. + /// + /// ***Panics*** if the `other` index is out of bounds. + /// + /// Computes in **O(1)** time (average). + pub fn swap_indices(self, other: usize) { + self.map.swap_indices(self.index, other) + } } impl fmt::Debug for IndexedEntry<'_, K, V> { diff --git a/src/map/core/raw.rs b/src/map/core/raw.rs index 233e41e7..a50bb615 100644 --- a/src/map/core/raw.rs +++ b/src/map/core/raw.rs @@ -142,4 +142,10 @@ impl<'a, K, V> RawTableEntry<'a, K, V> { let (index, _slot) = unsafe { self.map.indices.remove(self.raw_bucket) }; (self.map, index) } + + /// Take no action, just return the index and the original map reference. + pub(super) fn into_inner(self) -> (&'a mut IndexMapCore, usize) { + let index = self.index(); + (self.map, index) + } } diff --git a/src/map/core/raw_entry_v1.rs b/src/map/core/raw_entry_v1.rs index 68cdc741..7cf3e1ab 100644 --- a/src/map/core/raw_entry_v1.rs +++ b/src/map/core/raw_entry_v1.rs @@ -10,7 +10,7 @@ //! `IndexMap` without such an opt-in trait. use super::raw::RawTableEntry; -use super::{get_hash, IndexMapCore}; +use super::IndexMapCore; use crate::{Equivalent, HashValue, IndexMap}; use core::fmt; use core::hash::{BuildHasher, Hash, Hasher}; @@ -539,6 +539,30 @@ impl<'a, K, V, S> RawOccupiedEntryMut<'a, K, V, S> { let (map, index) = self.raw.remove_index(); map.shift_remove_finish(index) } + + /// Moves the position of the entry to a new index + /// by shifting all other entries in-between. + /// + /// * If `self.index() < to`, the other pairs will shift down while the targeted pair moves up. + /// * If `self.index() > to`, the other pairs will shift up while the targeted pair moves down. + /// + /// ***Panics*** if `to` is out of bounds. + /// + /// Computes in **O(n)** time (average). + pub fn move_index(self, to: usize) { + let (map, index) = self.raw.into_inner(); + map.move_index(index, to); + } + + /// Swaps the position of entry with another. + /// + /// ***Panics*** if the `other` index is out of bounds. + /// + /// Computes in **O(1)** time (average). + pub fn swap_indices(self, other: usize) { + let (map, index) = self.raw.into_inner(); + map.swap_indices(index, other) + } } /// A view into a vacant raw entry in an [`IndexMap`]. @@ -575,13 +599,43 @@ impl<'a, K, V, S> RawVacantEntryMut<'a, K, V, S> { /// Inserts the given key and value into the map with the provided hash, /// and returns mutable references to them. pub fn insert_hashed_nocheck(self, hash: u64, key: K, value: V) -> (&'a mut K, &'a mut V) { - let i = self.index(); - let map = self.map; let hash = HashValue(hash as usize); - map.indices.insert(hash.get(), i, get_hash(&map.entries)); - debug_assert_eq!(i, map.entries.len()); - map.push_entry(hash, key, value); - map.entries[i].muts() + let i = self.map.insert_unique(hash, key, value); + self.map.entries[i].muts() + } + + /// Inserts the given key and value into the map at the given index, + /// shifting others to the right, and returns mutable references to them. + /// + /// ***Panics*** if `index` is out of bounds. + /// + /// Computes in **O(n)** time (average). + pub fn shift_insert(self, index: usize, key: K, value: V) -> (&'a mut K, &'a mut V) + where + K: Hash, + S: BuildHasher, + { + let mut h = self.hash_builder.build_hasher(); + key.hash(&mut h); + self.shift_insert_hashed_nocheck(index, h.finish(), key, value) + } + + /// Inserts the given key and value into the map with the provided hash + /// at the given index, and returns mutable references to them. + /// + /// ***Panics*** if `index` is out of bounds. + /// + /// Computes in **O(n)** time (average). + pub fn shift_insert_hashed_nocheck( + self, + index: usize, + hash: u64, + key: K, + value: V, + ) -> (&'a mut K, &'a mut V) { + let hash = HashValue(hash as usize); + self.map.shift_insert_unique(index, hash, key, value); + self.map.entries[index].muts() } } From ec26c8e38c2bfadae38922c0508950bb12611eea Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sat, 10 Feb 2024 20:12:22 -0800 Subject: [PATCH 2/8] Add quickchecks for new methods --- tests/quick.rs | 167 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 165 insertions(+), 2 deletions(-) diff --git a/tests/quick.rs b/tests/quick.rs index 4142e2d6..44f6e11b 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -218,7 +218,7 @@ quickcheck_limit! { } // Use `u8` test indices so quickcheck is less likely to go out of bounds. - fn swap_indices(vec: Vec, a: u8, b: u8) -> TestResult { + fn set_swap_indices(vec: Vec, a: u8, b: u8) -> TestResult { let mut set = IndexSet::::from_iter(vec); let a = usize::from(a); let b = usize::from(b); @@ -240,8 +240,39 @@ quickcheck_limit! { TestResult::passed() } + fn map_swap_indices(vec: Vec, from: u8, to: u8) -> TestResult { + test_map_swap_indices(vec, from, to, IndexMap::swap_indices) + } + + fn occupied_entry_swap_indices(vec: Vec, from: u8, to: u8) -> TestResult { + test_map_swap_indices(vec, from, to, |map, from, to| { + let key = map.keys()[from]; + match map.entry(key) { + OEntry::Occupied(entry) => entry.swap_indices(to), + _ => unreachable!(), + } + }) + } + + fn indexed_entry_swap_indices(vec: Vec, from: u8, to: u8) -> TestResult { + test_map_swap_indices(vec, from, to, |map, from, to| { + map.get_index_entry(from).unwrap().swap_indices(to); + }) + } + + fn raw_occupied_entry_swap_indices(vec: Vec, from: u8, to: u8) -> TestResult { + use indexmap::map::raw_entry_v1::{RawEntryApiV1, RawEntryMut}; + test_map_swap_indices(vec, from, to, |map, from, to| { + let key = map.keys()[from]; + match map.raw_entry_mut_v1().from_key(&key) { + RawEntryMut::Occupied(entry) => entry.swap_indices(to), + _ => unreachable!(), + } + }) + } + // Use `u8` test indices so quickcheck is less likely to go out of bounds. - fn move_index(vec: Vec, from: u8, to: u8) -> TestResult { + fn set_move_index(vec: Vec, from: u8, to: u8) -> TestResult { let mut set = IndexSet::::from_iter(vec); let from = usize::from(from); let to = usize::from(to); @@ -263,6 +294,138 @@ quickcheck_limit! { })); TestResult::passed() } + + fn map_move_index(vec: Vec, from: u8, to: u8) -> TestResult { + test_map_move_index(vec, from, to, IndexMap::move_index) + } + + fn occupied_entry_move_index(vec: Vec, from: u8, to: u8) -> TestResult { + test_map_move_index(vec, from, to, |map, from, to| { + let key = map.keys()[from]; + match map.entry(key) { + OEntry::Occupied(entry) => entry.move_index(to), + _ => unreachable!(), + } + }) + } + + fn indexed_entry_move_index(vec: Vec, from: u8, to: u8) -> TestResult { + test_map_move_index(vec, from, to, |map, from, to| { + map.get_index_entry(from).unwrap().move_index(to); + }) + } + + fn raw_occupied_entry_move_index(vec: Vec, from: u8, to: u8) -> TestResult { + use indexmap::map::raw_entry_v1::{RawEntryApiV1, RawEntryMut}; + test_map_move_index(vec, from, to, |map, from, to| { + let key = map.keys()[from]; + match map.raw_entry_mut_v1().from_key(&key) { + RawEntryMut::Occupied(entry) => entry.move_index(to), + _ => unreachable!(), + } + }) + } + + fn occupied_entry_shift_insert(vec: Vec, i: u8) -> TestResult { + test_map_shift_insert(vec, i, |map, i, key| { + match map.entry(key) { + OEntry::Vacant(entry) => entry.shift_insert(i, ()), + _ => unreachable!(), + }; + }) + } + + fn raw_occupied_entry_shift_insert(vec: Vec, i: u8) -> TestResult { + use indexmap::map::raw_entry_v1::{RawEntryApiV1, RawEntryMut}; + test_map_shift_insert(vec, i, |map, i, key| { + match map.raw_entry_mut_v1().from_key(&key) { + RawEntryMut::Vacant(entry) => entry.shift_insert(i, key, ()), + _ => unreachable!(), + }; + }) + } +} + +fn test_map_swap_indices(vec: Vec, a: u8, b: u8, swap_indices: F) -> TestResult +where + F: FnOnce(&mut IndexMap, usize, usize), +{ + let mut map = IndexMap::::from_iter(vec.into_iter().map(|k| (k, ()))); + let a = usize::from(a); + let b = usize::from(b); + + if a >= map.len() || b >= map.len() { + return TestResult::discard(); + } + + let mut vec = Vec::from_iter(map.keys().copied()); + vec.swap(a, b); + + swap_indices(&mut map, a, b); + + // Check both iteration order and hash lookups + assert!(map.keys().eq(vec.iter())); + assert!(vec + .iter() + .enumerate() + .all(|(i, x)| { map.get_index_of(x) == Some(i) })); + TestResult::passed() +} + +fn test_map_move_index(vec: Vec, from: u8, to: u8, move_index: F) -> TestResult +where + F: FnOnce(&mut IndexMap, usize, usize), +{ + let mut map = IndexMap::::from_iter(vec.into_iter().map(|k| (k, ()))); + let from = usize::from(from); + let to = usize::from(to); + + if from >= map.len() || to >= map.len() { + return TestResult::discard(); + } + + let mut vec = Vec::from_iter(map.keys().copied()); + let x = vec.remove(from); + vec.insert(to, x); + + move_index(&mut map, from, to); + + // Check both iteration order and hash lookups + assert!(map.keys().eq(vec.iter())); + assert!(vec + .iter() + .enumerate() + .all(|(i, x)| { map.get_index_of(x) == Some(i) })); + TestResult::passed() +} + +fn test_map_shift_insert(vec: Vec, i: u8, shift_insert: F) -> TestResult +where + F: FnOnce(&mut IndexMap, usize, u8), +{ + let mut map = IndexMap::::from_iter(vec.into_iter().map(|k| (k, ()))); + let i = usize::from(i); + if i >= map.len() { + return TestResult::discard(); + } + + let mut vec = Vec::from_iter(map.keys().copied()); + let x = vec.pop().unwrap(); + vec.insert(i, x); + + let (last, ()) = map.pop().unwrap(); + assert_eq!(x, last); + map.shrink_to_fit(); // so we might have to grow and rehash the table + + shift_insert(&mut map, i, last); + + // Check both iteration order and hash lookups + assert!(map.keys().eq(vec.iter())); + assert!(vec + .iter() + .enumerate() + .all(|(i, x)| { map.get_index_of(x) == Some(i) })); + TestResult::passed() } use crate::Op::*; From 3264695254da7275edefdc1c3ae1576199777cb9 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sat, 10 Feb 2024 20:14:14 -0800 Subject: [PATCH 3/8] quick: use more normal Entry names --- tests/quick.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/quick.rs b/tests/quick.rs index 44f6e11b..c32d0b91 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -19,8 +19,8 @@ use std::hash::Hash; use std::ops::Bound; use std::ops::Deref; -use indexmap::map::Entry as OEntry; -use std::collections::hash_map::Entry as HEntry; +use indexmap::map::Entry; +use std::collections::hash_map::Entry as StdEntry; fn set<'a, T: 'a, I>(iter: I) -> HashSet where @@ -248,7 +248,7 @@ quickcheck_limit! { test_map_swap_indices(vec, from, to, |map, from, to| { let key = map.keys()[from]; match map.entry(key) { - OEntry::Occupied(entry) => entry.swap_indices(to), + Entry::Occupied(entry) => entry.swap_indices(to), _ => unreachable!(), } }) @@ -303,7 +303,7 @@ quickcheck_limit! { test_map_move_index(vec, from, to, |map, from, to| { let key = map.keys()[from]; match map.entry(key) { - OEntry::Occupied(entry) => entry.move_index(to), + Entry::Occupied(entry) => entry.move_index(to), _ => unreachable!(), } }) @@ -329,7 +329,7 @@ quickcheck_limit! { fn occupied_entry_shift_insert(vec: Vec, i: u8) -> TestResult { test_map_shift_insert(vec, i, |map, i, key| { match map.entry(key) { - OEntry::Vacant(entry) => entry.shift_insert(i, ()), + Entry::Vacant(entry) => entry.shift_insert(i, ()), _ => unreachable!(), }; }) @@ -473,10 +473,10 @@ where b.remove(k); } RemoveEntry(ref k) => { - if let OEntry::Occupied(ent) = a.entry(k.clone()) { + if let Entry::Occupied(ent) = a.entry(k.clone()) { ent.swap_remove_entry(); } - if let HEntry::Occupied(ent) = b.entry(k.clone()) { + if let StdEntry::Occupied(ent) = b.entry(k.clone()) { ent.remove_entry(); } } From 209e3e16cf8f007a8a0e7de871105f4cba50a07d Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sun, 11 Feb 2024 12:56:11 -0800 Subject: [PATCH 4/8] Document equivalence of move_index/swap_indices --- src/map/core/entry.rs | 12 ++++++++++++ src/map/core/raw_entry_v1.rs | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/src/map/core/entry.rs b/src/map/core/entry.rs index b329179d..48c00294 100644 --- a/src/map/core/entry.rs +++ b/src/map/core/entry.rs @@ -241,6 +241,9 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { /// Moves the position of the entry to a new index /// by shifting all other entries in-between. /// + /// This is equivalent to [`IndexMap::move_index`][`crate::IndexMap::move_index`] + /// coming `from` the current [`.index()`][Self::index]. + /// /// * If `self.index() < to`, the other pairs will shift down while the targeted pair moves up. /// * If `self.index() > to`, the other pairs will shift up while the targeted pair moves down. /// @@ -254,6 +257,9 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { /// Swaps the position of entry with another. /// + /// This is equivalent to [`IndexMap::swap_indices`][`crate::IndexMap::swap_indices`] + /// with the current [`.index()`][Self::index] as one of the two being swapped. + /// /// ***Panics*** if the `other` index is out of bounds. /// /// Computes in **O(1)** time (average). @@ -420,6 +426,9 @@ impl<'a, K, V> IndexedEntry<'a, K, V> { /// Moves the position of the entry to a new index /// by shifting all other entries in-between. /// + /// This is equivalent to [`IndexMap::move_index`][`crate::IndexMap::move_index`] + /// coming `from` the current [`.index()`][Self::index]. + /// /// * If `self.index() < to`, the other pairs will shift down while the targeted pair moves up. /// * If `self.index() > to`, the other pairs will shift up while the targeted pair moves down. /// @@ -432,6 +441,9 @@ impl<'a, K, V> IndexedEntry<'a, K, V> { /// Swaps the position of entry with another. /// + /// This is equivalent to [`IndexMap::swap_indices`][`crate::IndexMap::swap_indices`] + /// with the current [`.index()`][Self::index] as one of the two being swapped. + /// /// ***Panics*** if the `other` index is out of bounds. /// /// Computes in **O(1)** time (average). diff --git a/src/map/core/raw_entry_v1.rs b/src/map/core/raw_entry_v1.rs index 7cf3e1ab..a3848149 100644 --- a/src/map/core/raw_entry_v1.rs +++ b/src/map/core/raw_entry_v1.rs @@ -543,6 +543,9 @@ impl<'a, K, V, S> RawOccupiedEntryMut<'a, K, V, S> { /// Moves the position of the entry to a new index /// by shifting all other entries in-between. /// + /// This is equivalent to [`IndexMap::move_index`] + /// coming `from` the current [`.index()`][Self::index]. + /// /// * If `self.index() < to`, the other pairs will shift down while the targeted pair moves up. /// * If `self.index() > to`, the other pairs will shift up while the targeted pair moves down. /// @@ -556,6 +559,9 @@ impl<'a, K, V, S> RawOccupiedEntryMut<'a, K, V, S> { /// Swaps the position of entry with another. /// + /// This is equivalent to [`IndexMap::swap_indices`] + /// with the current [`.index()`][Self::index] as one of the two being swapped. + /// /// ***Panics*** if the `other` index is out of bounds. /// /// Computes in **O(1)** time (average). From 3b217ca498d037087c86d72203753ac650fddc9f Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sun, 11 Feb 2024 12:59:35 -0800 Subject: [PATCH 5/8] Add `IndexMap::shift_insert` based on `Entry` --- src/map.rs | 38 ++++++++++++++++++++++++++++++++++---- src/map/tests.rs | 19 +++++++++++++++++++ 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/map.rs b/src/map.rs index 93cd82f3..2c27f559 100644 --- a/src/map.rs +++ b/src/map.rs @@ -26,6 +26,7 @@ pub use crate::rayon::map as rayon; use ::core::cmp::Ordering; use ::core::fmt; use ::core::hash::{BuildHasher, Hash, Hasher}; +use ::core::mem; use ::core::ops::{Index, IndexMut, RangeBounds}; use alloc::boxed::Box; use alloc::vec::Vec; @@ -380,14 +381,14 @@ where /// /// If an equivalent key already exists in the map: the key remains and /// retains in its place in the order, its corresponding value is updated - /// with `value` and the older value is returned inside `Some(_)`. + /// with `value`, and the older value is returned inside `Some(_)`. /// /// If no equivalent key existed in the map: the new key-value pair is /// inserted, last in order, and `None` is returned. /// /// Computes in **O(1)** time (amortized average). /// - /// See also [`entry`][Self::entry] if you you want to insert *or* modify, + /// See also [`entry`][Self::entry] if you want to insert *or* modify, /// or [`insert_full`][Self::insert_full] if you need to get the index of /// the corresponding key-value pair. pub fn insert(&mut self, key: K, value: V) -> Option { @@ -398,19 +399,48 @@ where /// /// If an equivalent key already exists in the map: the key remains and /// retains in its place in the order, its corresponding value is updated - /// with `value` and the older value is returned inside `(index, Some(_))`. + /// with `value`, and the older value is returned inside `(index, Some(_))`. /// /// If no equivalent key existed in the map: the new key-value pair is /// inserted, last in order, and `(index, None)` is returned. /// /// Computes in **O(1)** time (amortized average). /// - /// See also [`entry`][Self::entry] if you you want to insert *or* modify. + /// See also [`entry`][Self::entry] if you want to insert *or* modify. pub fn insert_full(&mut self, key: K, value: V) -> (usize, Option) { let hash = self.hash(&key); self.core.insert_full(hash, key, value) } + /// 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 new position in the map, its corresponding value is updated + /// with `value`, and the older value is returned inside `Some(_)`. + /// + /// 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. + /// + /// 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`]. + pub fn shift_insert(&mut self, index: usize, key: K, value: V) -> Option { + match self.entry(key) { + Entry::Occupied(mut entry) => { + let old = mem::replace(entry.get_mut(), value); + entry.move_index(index); + Some(old) + } + Entry::Vacant(entry) => { + entry.shift_insert(index, value); + None + } + } + } + /// Get the given key’s corresponding entry in the map for insertion and/or /// in-place manipulation. /// diff --git a/src/map/tests.rs b/src/map/tests.rs index d81d51b9..1bc2980d 100644 --- a/src/map/tests.rs +++ b/src/map/tests.rs @@ -108,6 +108,25 @@ fn insert_order() { } } +#[test] +fn shift_insert() { + let insert = [0, 4, 2, 12, 8, 7, 11, 5, 3, 17, 19, 22, 23]; + let mut map = IndexMap::new(); + + for &elt in &insert { + map.shift_insert(0, elt, ()); + } + + assert_eq!(map.keys().count(), map.len()); + assert_eq!(map.keys().count(), insert.len()); + for (a, b) in insert.iter().rev().zip(map.keys()) { + assert_eq!(a, b); + } + for (i, k) in (0..insert.len()).zip(map.keys()) { + assert_eq!(map.get_index(i).unwrap().0, k); + } +} + #[test] fn grow() { let insert = [0, 4, 2, 12, 8, 7, 11]; From 4572493c53ecb74b37d6d3aed53007f3322a9329 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sun, 11 Feb 2024 13:01:32 -0800 Subject: [PATCH 6/8] Add `IndexSet::shift_insert` based on map's method --- src/set.rs | 14 ++++++++++++++ src/set/tests.rs | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/set.rs b/src/set.rs index e2560843..d52b0382 100644 --- a/src/set.rs +++ b/src/set.rs @@ -353,6 +353,20 @@ where (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`. + /// + /// ***Panics*** if `index` is out of bounds. + /// + /// Computes in **O(n)** time (average). + pub fn shift_insert(&mut self, index: usize, value: T) -> bool { + self.map.shift_insert(index, value, ()).is_none() + } + /// Adds a value to the set, replacing the existing value, if any, that is /// equal to the given one, without altering its insertion order. Returns /// the replaced value. diff --git a/src/set/tests.rs b/src/set/tests.rs index a02d8a4f..a67e5d37 100644 --- a/src/set/tests.rs +++ b/src/set/tests.rs @@ -127,6 +127,25 @@ fn insert_order() { } } +#[test] +fn shift_insert() { + let insert = [0, 4, 2, 12, 8, 7, 11, 5, 3, 17, 19, 22, 23]; + let mut set = IndexSet::new(); + + for &elt in &insert { + set.shift_insert(0, elt); + } + + assert_eq!(set.iter().count(), set.len()); + assert_eq!(set.iter().count(), insert.len()); + for (a, b) in insert.iter().rev().zip(set.iter()) { + assert_eq!(a, b); + } + for (i, v) in (0..insert.len()).zip(set.iter()) { + assert_eq!(set.get_index(i).unwrap(), v); + } +} + #[test] fn replace() { let replace = [0, 4, 2, 12, 8, 7, 11, 5]; From 5debe7378d0289997b2092c9f685c6f64e20b880 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sun, 11 Feb 2024 13:15:44 -0800 Subject: [PATCH 7/8] IndexSet::swap_indices is O(1) too --- src/set.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/set.rs b/src/set.rs index d52b0382..801fd43f 100644 --- a/src/set.rs +++ b/src/set.rs @@ -902,6 +902,8 @@ impl IndexSet { /// Swaps the position of two values in the set. /// /// ***Panics*** if `a` or `b` are out of bounds. + /// + /// Computes in **O(1)** time (average). pub fn swap_indices(&mut self, a: usize, b: usize) { self.map.swap_indices(a, b) } From 8c206ef7922d8d171fdbb1087c5935e55bb6fb1d Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sun, 11 Feb 2024 13:23:25 -0800 Subject: [PATCH 8/8] Test shift_insert that moves --- src/map/tests.rs | 8 ++++++++ src/set/tests.rs | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/src/map/tests.rs b/src/map/tests.rs index 1bc2980d..bba78ff5 100644 --- a/src/map/tests.rs +++ b/src/map/tests.rs @@ -125,6 +125,14 @@ fn shift_insert() { for (i, k) in (0..insert.len()).zip(map.keys()) { assert_eq!(map.get_index(i).unwrap().0, k); } + + // "insert" that moves an existing entry + map.shift_insert(0, insert[0], ()); + assert_eq!(map.keys().count(), insert.len()); + assert_eq!(insert[0], map.keys()[0]); + for (a, b) in insert[1..].iter().rev().zip(map.keys().skip(1)) { + assert_eq!(a, b); + } } #[test] diff --git a/src/set/tests.rs b/src/set/tests.rs index a67e5d37..35a076e8 100644 --- a/src/set/tests.rs +++ b/src/set/tests.rs @@ -144,6 +144,14 @@ fn shift_insert() { for (i, v) in (0..insert.len()).zip(set.iter()) { assert_eq!(set.get_index(i).unwrap(), v); } + + // "insert" that moves an existing entry + set.shift_insert(0, insert[0]); + assert_eq!(set.iter().count(), insert.len()); + assert_eq!(insert[0], set[0]); + for (a, b) in insert[1..].iter().rev().zip(set.iter().skip(1)) { + assert_eq!(a, b); + } } #[test]