From b44138b20463ea9ec804f7213ccf68823126b682 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 21 Aug 2024 12:15:10 -0700 Subject: [PATCH 01/11] Move the `MutableEntryKey::key_mut` doc to the trait --- src/map/mutable.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/map/mutable.rs b/src/map/mutable.rs index c778971b..355eeb21 100644 --- a/src/map/mutable.rs +++ b/src/map/mutable.rs @@ -106,6 +106,9 @@ where /// This trait is sealed and cannot be implemented for types outside this crate. pub trait MutableEntryKey: private::Sealed { type Key; + + /// Gets a mutable reference to the entry's key, either within the map if occupied, + /// or else the new key that was used to find the entry. fn key_mut(&mut self) -> &mut Self::Key; } @@ -114,9 +117,6 @@ pub trait MutableEntryKey: private::Sealed { /// See [`MutableEntryKey`] for more information. impl MutableEntryKey for Entry<'_, K, V> { type Key = K; - - /// Gets a mutable reference to the entry's key, either within the map if occupied, - /// or else the new key that was used to find the entry. fn key_mut(&mut self) -> &mut Self::Key { match self { Entry::Occupied(e) => e.key_mut(), From 9b93dd576d3c1c7d1a3a8e61b4bf72abf110d9ee Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 21 Aug 2024 15:22:14 -0700 Subject: [PATCH 02/11] Update `package.metadata.release` --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index cdcc77b3..535c1a4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,8 @@ test_debug = [] debug = true [package.metadata.release] -no-dev-version = true +allow-branch = ["master"] +sign-tag = true tag-name = "{{version}}" [package.metadata.docs.rs] From ba169812e6361fee03441c1a2bcfdda74767dd3c Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 27 Aug 2024 09:34:03 -0700 Subject: [PATCH 03/11] Implement `From` between `IndexedEntry` and `OccupiedEntry` --- src/map/core/entry.rs | 18 ++++++++++++++++++ src/map/core/raw.rs | 23 +++++++++++++++++------ src/map/tests.rs | 25 +++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/src/map/core/entry.rs b/src/map/core/entry.rs index ab45ecc1..5ac8c495 100644 --- a/src/map/core/entry.rs +++ b/src/map/core/entry.rs @@ -282,6 +282,17 @@ impl fmt::Debug for OccupiedEntry<'_, K, V> { } } +impl<'a, K, V> From> for OccupiedEntry<'a, K, V> { + fn from(entry: IndexedEntry<'a, K, V>) -> Self { + Self { + raw: entry + .map + .index_raw_entry(entry.index) + .expect("index not found"), + } + } +} + /// A view into a vacant entry in an [`IndexMap`][crate::IndexMap]. /// It is part of the [`Entry`] enum. pub struct VacantEntry<'a, K, V> { @@ -491,3 +502,10 @@ impl fmt::Debug for IndexedEntry<'_, K, V> { .finish() } } + +impl<'a, K, V> From> for IndexedEntry<'a, K, V> { + fn from(entry: OccupiedEntry<'a, K, V>) -> Self { + let (map, index) = entry.raw.into_inner(); + Self { map, index } + } +} diff --git a/src/map/core/raw.rs b/src/map/core/raw.rs index 45199433..c6a7b696 100644 --- a/src/map/core/raw.rs +++ b/src/map/core/raw.rs @@ -83,16 +83,19 @@ impl IndexMapCore { let entries = &*self.entries; let eq = move |&i: &usize| is_match(&entries[i].key); match self.indices.find(hash.get(), eq) { - // SAFETY: The entry is created with a live raw bucket, at the same time - // we have a &mut reference to the map, so it can not be modified further. - Some(raw_bucket) => Ok(RawTableEntry { - map: self, - raw_bucket, - }), + // SAFETY: The bucket is valid because we *just* found it in this map. + Some(raw_bucket) => Ok(unsafe { RawTableEntry::new(self, raw_bucket) }), None => Err(self), } } + pub(super) fn index_raw_entry(&mut self, index: usize) -> Option> { + let hash = self.entries.get(index)?.hash; + let raw_bucket = self.indices.find(hash.get(), move |&i| i == index)?; + // SAFETY: The bucket is valid because we *just* found it in this map. + Some(unsafe { RawTableEntry::new(self, raw_bucket) }) + } + pub(super) fn indices_mut(&mut self) -> impl Iterator { // SAFETY: we're not letting any of the buckets escape this function, // only the item references that are appropriately bound to `&mut self`. @@ -113,6 +116,13 @@ pub(super) struct RawTableEntry<'a, K, V> { unsafe impl Sync for RawTableEntry<'_, K, V> {} impl<'a, K, V> RawTableEntry<'a, K, V> { + /// The caller must ensure that the `raw_bucket` is valid in the given `map`, + /// and then we hold the `&mut` reference for exclusive access. + #[inline] + unsafe fn new(map: &'a mut IndexMapCore, raw_bucket: RawBucket) -> Self { + Self { map, raw_bucket } + } + /// Return the index of the key-value pair #[inline] pub(super) fn index(&self) -> usize { @@ -146,6 +156,7 @@ impl<'a, K, V> RawTableEntry<'a, K, V> { } /// Take no action, just return the index and the original map reference. + #[inline] pub(super) fn into_inner(self) -> (&'a mut IndexMapCore, usize) { let index = self.index(); (self.map, index) diff --git a/src/map/tests.rs b/src/map/tests.rs index 49541181..00600892 100644 --- a/src/map/tests.rs +++ b/src/map/tests.rs @@ -416,6 +416,31 @@ fn get_index_entry() { assert_eq!(*map.get(&3).unwrap(), "4"); } +#[test] +fn from_entries() { + let mut map = IndexMap::from([(1, "1"), (2, "2"), (3, "3")]); + + { + let e = match map.entry(1) { + Entry::Occupied(e) => IndexedEntry::from(e), + Entry::Vacant(_) => panic!(), + }; + assert_eq!(e.index(), 0); + assert_eq!(*e.key(), 1); + assert_eq!(*e.get(), "1"); + } + + { + let e = match map.get_index_entry(1) { + Some(e) => OccupiedEntry::from(e), + None => panic!(), + }; + assert_eq!(e.index(), 1); + assert_eq!(*e.key(), 2); + assert_eq!(*e.get(), "2"); + } +} + #[test] fn keys() { let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; From e1f5f26e8a76836bcb3af2e16818e2b6a8f7477f Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Mon, 26 Aug 2024 18:52:20 -0700 Subject: [PATCH 04/11] Add `first_entry` and `last_entry` similar to `BTreeMap` --- src/map.rs | 14 ++++++++++++++ src/map/tests.rs | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/map.rs b/src/map.rs index 85310934..956812e4 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1098,6 +1098,13 @@ impl IndexMap { self.as_entries_mut().first_mut().map(Bucket::ref_mut) } + /// Get the first entry in the map for in-place manipulation. + /// + /// Computes in **O(1)** time. + pub fn first_entry(&mut self) -> Option> { + self.get_index_entry(0) + } + /// Get the last key-value pair /// /// Computes in **O(1)** time. @@ -1112,6 +1119,13 @@ impl IndexMap { self.as_entries_mut().last_mut().map(Bucket::ref_mut) } + /// Get the last entry in the map for in-place manipulation. + /// + /// Computes in **O(1)** time. + pub fn last_entry(&mut self) -> Option> { + self.get_index_entry(self.len().checked_sub(1)?) + } + /// Remove the key-value pair by index /// /// Valid indices are *0 <= index < self.len()* diff --git a/src/map/tests.rs b/src/map/tests.rs index 49541181..fd62904d 100644 --- a/src/map/tests.rs +++ b/src/map/tests.rs @@ -391,6 +391,8 @@ fn get_index_entry() { let mut map = IndexMap::new(); assert!(map.get_index_entry(0).is_none()); + assert!(map.first_entry().is_none()); + assert!(map.last_entry().is_none()); map.insert(0, "0"); map.insert(1, "1"); @@ -414,6 +416,18 @@ fn get_index_entry() { } assert_eq!(*map.get(&3).unwrap(), "4"); + + { + let e = map.first_entry().unwrap(); + assert_eq!(*e.key(), 0); + assert_eq!(*e.get(), "0"); + } + + { + let e = map.last_entry().unwrap(); + assert_eq!(*e.key(), 2); + assert_eq!(*e.get(), "2"); + } } #[test] From 264e5b73045b1f28d36f80c7e3d0bac63af5e887 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 27 Aug 2024 08:50:05 -0700 Subject: [PATCH 05/11] Add doc aliases like `BTreeMap`/`BTreeSet` --- src/map.rs | 3 +++ src/set.rs | 1 + 2 files changed, 4 insertions(+) diff --git a/src/map.rs b/src/map.rs index 956812e4..b224aace 100644 --- a/src/map.rs +++ b/src/map.rs @@ -821,6 +821,7 @@ impl IndexMap { /// This preserves the order of the remaining elements. /// /// Computes in **O(1)** time (average). + #[doc(alias = "pop_last")] // like `BTreeMap` pub fn pop(&mut self) -> Option<(K, V)> { self.core.pop() } @@ -1087,6 +1088,7 @@ impl IndexMap { /// Get the first key-value pair /// /// Computes in **O(1)** time. + #[doc(alias = "first_key_value")] // like `BTreeMap` pub fn first(&self) -> Option<(&K, &V)> { self.as_entries().first().map(Bucket::refs) } @@ -1108,6 +1110,7 @@ impl IndexMap { /// Get the last key-value pair /// /// Computes in **O(1)** time. + #[doc(alias = "last_key_value")] // like `BTreeMap` pub fn last(&self) -> Option<(&K, &V)> { self.as_entries().last().map(Bucket::refs) } diff --git a/src/set.rs b/src/set.rs index 835ccf02..7a8ac4df 100644 --- a/src/set.rs +++ b/src/set.rs @@ -708,6 +708,7 @@ impl IndexSet { /// This preserves the order of the remaining elements. /// /// Computes in **O(1)** time (average). + #[doc(alias = "pop_last")] // like `BTreeSet` pub fn pop(&mut self) -> Option { self.map.pop().map(|(x, ())| x) } From 922c6ad1afebfa7c6f5781fdb4fcba88f1cb2e0a Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 28 Aug 2024 14:37:54 -0700 Subject: [PATCH 06/11] Update the CI badge --- .github/workflows/ci.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 312f4533..98c77507 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ on: branches: [ master ] merge_group: -name: Continuous integration +name: CI env: CARGO_TERM_COLOR: always diff --git a/README.md b/README.md index e504109d..2585b232 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # indexmap -[![build status](https://github.com/indexmap-rs/indexmap/workflows/Continuous%20integration/badge.svg?branch=master)](https://github.com/indexmap-rs/indexmap/actions) +[![build status](https://github.com/indexmap-rs/indexmap/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/indexmap-rs/indexmap/actions) [![crates.io](https://img.shields.io/crates/v/indexmap.svg)](https://crates.io/crates/indexmap) [![docs](https://docs.rs/indexmap/badge.svg)](https://docs.rs/indexmap) [![rustc](https://img.shields.io/badge/rust-1.63%2B-orange.svg)](https://img.shields.io/badge/rust-1.63%2B-orange.svg) From 0247a1555de940982260384101946d3f958452e5 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 27 Aug 2024 15:48:17 -0700 Subject: [PATCH 07/11] Document and assert index bounds in `shift_insert` --- src/map.rs | 8 +++++++- src/set.rs | 11 +++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/map.rs b/src/map.rs index b224aace..283970d2 100644 --- a/src/map.rs +++ b/src/map.rs @@ -448,26 +448,32 @@ where /// 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 + /// 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()`! /// /// 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`]. pub fn shift_insert(&mut self, index: usize, key: K, value: V) -> Option { + 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 } diff --git a/src/set.rs b/src/set.rs index 7a8ac4df..1f5f84f9 100644 --- a/src/set.rs +++ b/src/set.rs @@ -385,12 +385,15 @@ where /// 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()`! + /// + /// 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). pub fn shift_insert(&mut self, index: usize, value: T) -> bool { From 7224def0106a4a6074a0d4619ce99d4120b859a8 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 27 Aug 2024 15:51:27 -0700 Subject: [PATCH 08/11] Add `insert_before` as an alternate to `shift_insert` --- src/map.rs | 40 ++++++++++++++++++++++++++++++++++++++++ src/set.rs | 19 +++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/src/map.rs b/src/map.rs index 283970d2..d8741f1d 100644 --- a/src/map.rs +++ b/src/map.rs @@ -445,12 +445,52 @@ where } } + /// 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`]. + pub fn insert_before(&mut self, mut index: usize, key: K, value: V) -> (usize, Option) { + 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. diff --git a/src/set.rs b/src/set.rs index 1f5f84f9..b1603047 100644 --- a/src/set.rs +++ b/src/set.rs @@ -383,11 +383,30 @@ 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). + 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 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`. /// From 8ca01b0df72a4914b2248a65087ce67e3711f52d Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 27 Aug 2024 15:52:26 -0700 Subject: [PATCH 09/11] Use `insert_before` for "new" entries in `insert_sorted` The only difference compared to using `shift_insert` is when the binary-searched key isn't *actually* new to the map, just not found in properly sorted order. In this case we can't guarantee a sorted result either, but it will at least behave better about the new position, especially if that's the end. --- src/map.rs | 4 ++-- src/map/tests.rs | 28 ++++++++++++++++++++++++++++ src/set.rs | 2 +- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/map.rs b/src/map.rs index d8741f1d..7a7b6c2b 100644 --- a/src/map.rs +++ b/src/map.rs @@ -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 @@ -441,7 +441,7 @@ 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), } } diff --git a/src/map/tests.rs b/src/map/tests.rs index 0c7d93b3..cbd4b4f2 100644 --- a/src/map/tests.rs +++ b/src/map/tests.rs @@ -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]; diff --git a/src/set.rs b/src/set.rs index b1603047..c7b97502 100644 --- a/src/set.rs +++ b/src/set.rs @@ -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 From 1d9b5e3d0345aacf296eec8515746b3dfb81f97d Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 29 Aug 2024 16:58:37 -0700 Subject: [PATCH 10/11] Add doc examples for `insert_before` and `shift_insert` --- src/map.rs | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/set.rs | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) diff --git a/src/map.rs b/src/map.rs index 7a7b6c2b..019c7f6d 100644 --- a/src/map.rs +++ b/src/map.rs @@ -463,6 +463,36 @@ where /// /// 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 = ('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) { assert!(index <= self.len(), "index out of bounds"); match self.entry(key) { @@ -503,6 +533,44 @@ where /// /// 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 = ('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 = ('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 { let len = self.len(); match self.entry(key) { diff --git a/src/set.rs b/src/set.rs index c7b97502..250e31b3 100644 --- a/src/set.rs +++ b/src/set.rs @@ -396,6 +396,36 @@ where /// Valid indices are `0..=set.len()` (inclusive). /// /// Computes in **O(n)** time (average). + /// + /// # Examples + /// + /// ``` + /// use indexmap::IndexSet; + /// let mut set: IndexSet = ('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()) @@ -415,6 +445,44 @@ where /// `0..=set.len()` (inclusive) when inserting a new value. /// /// Computes in **O(n)** time (average). + /// + /// # Examples + /// + /// ``` + /// use indexmap::IndexSet; + /// let mut set: IndexSet = ('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 = ('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() } From 48ed49017c9af536bda2916c78e77b619163a2f2 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 30 Aug 2024 11:48:17 -0700 Subject: [PATCH 11/11] Release 2.5.0 --- Cargo.toml | 2 +- RELEASES.md | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 535c1a4e..4590d455 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "indexmap" edition = "2021" -version = "2.4.0" +version = "2.5.0" documentation = "https://docs.rs/indexmap/" repository = "https://github.com/indexmap-rs/indexmap" license = "Apache-2.0 OR MIT" diff --git a/RELEASES.md b/RELEASES.md index daf4493e..bfe8ce83 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,12 @@ # Releases +## 2.5.0 + +- Added an `insert_before` method to `IndexMap` and `IndexSet`, as an + alternative to `shift_insert` with different behavior on existing entries. +- Added `first_entry` and `last_entry` methods to `IndexMap`. +- Added `From` implementations between `IndexedEntry` and `OccupiedEntry`. + ## 2.4.0 - Added methods `IndexMap::append` and `IndexSet::append`, moving all items from