From bf293bd3c01e63a4862a93d7d8a463aced10f1f5 Mon Sep 17 00:00:00 2001 From: Anders Musikka Date: Sun, 20 Dec 2020 17:39:20 +0100 Subject: [PATCH 001/236] Fixspelling mistakes in documentation of IndexMap --- src/map.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/map.rs b/src/map.rs index 532791cf..bb182ed3 100644 --- a/src/map.rs +++ b/src/map.rs @@ -509,7 +509,7 @@ where /// /// Like `Vec::swap_remove`, the pair is removed by swapping it with the /// last element of the map and popping it off. **This perturbs - /// the postion of what used to be the last element!** + /// the position of what used to be the last element!** /// /// Return `None` if `key` is not in map. /// @@ -525,7 +525,7 @@ where /// /// Like `Vec::swap_remove`, the pair is removed by swapping it with the /// last element of the map and popping it off. **This perturbs - /// the postion of what used to be the last element!** + /// the position of what used to be the last element!** /// /// Return `None` if `key` is not in map. /// @@ -545,7 +545,7 @@ where /// /// Like `Vec::swap_remove`, the pair is removed by swapping it with the /// last element of the map and popping it off. **This perturbs - /// the postion of what used to be the last element!** + /// the position of what used to be the last element!** /// /// Return `None` if `key` is not in map. /// @@ -751,7 +751,7 @@ impl IndexMap { /// /// Like `Vec::swap_remove`, the pair is removed by swapping it with the /// last element of the map and popping it off. **This perturbs - /// the postion of what used to be the last element!** + /// the position of what used to be the last element!** /// /// Computes in **O(1)** time (average). pub fn swap_remove_index(&mut self, index: usize) -> Option<(K, V)> { From 29a4f1904efce0b44b14e891e3600adaf0944318 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 19 Jan 2021 17:29:59 -0800 Subject: [PATCH 002/236] Change OccupiedEntry::key() to return the existing key in the map The new behavior is consistent with `HashMap` entries in the standard library, and it has been our intent to match that API when possible. --- src/map.rs | 22 ++++++++++++++++++++++ src/map/core.rs | 3 +++ src/map/core/raw.rs | 7 ++++++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/map.rs b/src/map.rs index bb182ed3..e16e064a 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1648,6 +1648,28 @@ mod tests { assert_eq!(&mut TestEnum::DefaultValue, map.entry(2).or_default()); } + #[test] + fn occupied_entry_key() { + // These keys match hash and equality, but their addresses are distinct. + let (k1, k2) = (&mut 1, &mut 1); + let k1_ptr = k1 as *const i32; + let k2_ptr = k2 as *const i32; + assert_ne!(k1_ptr, k2_ptr); + + let mut map = IndexMap::new(); + map.insert(k1, "value"); + match map.entry(k2) { + Entry::Occupied(ref e) => { + // `OccupiedEntry::key` should reference the key in the map, + // not the key that was used to find the entry. + let ptr = *e.key() as *const i32; + assert_eq!(ptr, k1_ptr); + assert_ne!(ptr, k2_ptr); + }, + Entry::Vacant(_) => panic!(), + } + } + #[test] fn keys() { let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; diff --git a/src/map/core.rs b/src/map/core.rs index 02e99e07..9482b2f5 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -464,6 +464,8 @@ impl<'a, K, V> Entry<'a, K, V> { } } + /// Gets a reference to the entry's key, either within the map if occupied, + /// or else the new key that was used to find the entry. pub fn key(&self) -> &K { match *self { Entry::Occupied(ref entry) => entry.key(), @@ -583,6 +585,7 @@ pub struct VacantEntry<'a, K, V> { } impl<'a, K, V> VacantEntry<'a, K, V> { + /// Gets a reference to the key that was used to find the entry. pub fn key(&self) -> &K { &self.key } diff --git a/src/map/core/raw.rs b/src/map/core/raw.rs index 3388f123..be91b3d6 100644 --- a/src/map/core/raw.rs +++ b/src/map/core/raw.rs @@ -104,8 +104,13 @@ unsafe impl Sync for OccupiedEntry<'_, K, V> {} // The parent module also adds methods that don't threaten the unsafe encapsulation. impl<'a, K, V> OccupiedEntry<'a, K, V> { + /// Gets a reference to the entry's key in the map. + /// + /// Note that this is not the key that was used to find the entry. There may be an observable + /// difference if the key type has any distinguishing features outside of `Hash` and `Eq`, like + /// extra fields or the memory address of an allocation. pub fn key(&self) -> &K { - &self.key + &self.map.entries[self.index()].key } pub fn get(&self) -> &V { From 0d801a6c24195099c73612cfedeb96db3bead30f Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 19 Jan 2021 17:37:54 -0800 Subject: [PATCH 003/236] Document all entry methods --- src/map/core.rs | 9 +++++++++ src/map/core/raw.rs | 7 +++++++ 2 files changed, 16 insertions(+) diff --git a/src/map/core.rs b/src/map/core.rs index 9482b2f5..1c261a51 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -445,6 +445,9 @@ pub enum Entry<'a, K, V> { } impl<'a, K, V> Entry<'a, K, V> { + /// Inserts the given default value in the entry if it is vacant and returns a mutable + /// reference to it. Otherwise a mutable reference to an already existent value is returned. + /// /// Computes in **O(1)** time (amortized average). pub fn or_insert(self, default: V) -> &'a mut V { match self { @@ -453,6 +456,9 @@ impl<'a, K, V> Entry<'a, K, V> { } } + /// Inserts the result of the `call` function in the entry if it is vacant and returns a mutable + /// reference to it. Otherwise a mutable reference to an already existent value is returned. + /// /// Computes in **O(1)** time (amortized average). pub fn or_insert_with(self, call: F) -> &'a mut V where @@ -590,6 +596,7 @@ impl<'a, K, V> VacantEntry<'a, K, V> { &self.key } + /// Takes ownership of the key, leaving the entry vacant. pub fn into_key(self) -> K { self.key } @@ -599,6 +606,8 @@ impl<'a, K, V> VacantEntry<'a, K, V> { self.map.len() } + /// 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.map.push(self.hash, self.key, value); &mut self.map.entries[i].value diff --git a/src/map/core/raw.rs b/src/map/core/raw.rs index be91b3d6..0115b07f 100644 --- a/src/map/core/raw.rs +++ b/src/map/core/raw.rs @@ -113,10 +113,15 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { &self.map.entries[self.index()].key } + /// Gets a reference to the entry's value in the map. pub fn get(&self) -> &V { &self.map.entries[self.index()].value } + /// Gets a mutable reference to the entry's value in the map. + /// + /// If you need a reference which may outlive the destruction of the + /// `Entry` value, see `into_mut`. pub fn get_mut(&mut self) -> &mut V { let index = self.index(); &mut self.map.entries[index].value @@ -136,6 +141,8 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { unsafe { self.raw_bucket.read() } } + /// Converts into a mutable reference to the entry's value in the map, + /// with a lifetime bound to the map itself. pub fn into_mut(self) -> &'a mut V { let index = self.index(); &mut self.map.entries[index].value From eebb54cefe4d0062d08d0c7430f5b04894bd0704 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Mon, 22 Feb 2021 17:37:51 -0800 Subject: [PATCH 004/236] add Entry::or_insert_with_key like Rust 1.50 --- src/map/core.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/map/core.rs b/src/map/core.rs index 1c261a51..aaa3768a 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -470,6 +470,24 @@ impl<'a, K, V> Entry<'a, K, V> { } } + /// Inserts the result of the `call` function with a reference to the entry's key if it is + /// vacant, and returns a mutable reference to the new value. Otherwise a mutable reference to + /// an already existent value is returned. + /// + /// Computes in **O(1)** time (amortized average). + pub fn or_insert_with_key(self, call: F) -> &'a mut V + where + F: FnOnce(&K) -> V, + { + match self { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => { + let value = call(&entry.key); + entry.insert(value) + } + } + } + /// Gets a reference to the entry's key, either within the map if occupied, /// or else the new key that was used to find the entry. pub fn key(&self) -> &K { From c7da2199f88a2903e3e1d4e35d0a0f3875f8e846 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 4 Mar 2021 17:16:24 -0800 Subject: [PATCH 005/236] Continue #167 with more "postion" typos --- src/map/core.rs | 2 +- src/map/core/raw.rs | 2 +- src/set.rs | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/map/core.rs b/src/map/core.rs index aaa3768a..c4e725c9 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -563,7 +563,7 @@ impl OccupiedEntry<'_, K, V> { /// /// Like `Vec::swap_remove`, the pair is removed by swapping it with the /// last element of the map and popping it off. **This perturbs - /// the postion of what used to be the last element!** + /// the position of what used to be the last element!** /// /// Computes in **O(1)** time (average). pub fn swap_remove(self) -> V { diff --git a/src/map/core/raw.rs b/src/map/core/raw.rs index 0115b07f..168e1af3 100644 --- a/src/map/core/raw.rs +++ b/src/map/core/raw.rs @@ -152,7 +152,7 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { /// /// Like `Vec::swap_remove`, the pair is removed by swapping it with the /// last element of the map and popping it off. **This perturbs - /// the postion of what used to be the last element!** + /// the position of what used to be the last element!** /// /// Computes in **O(1)** time (average). pub fn swap_remove_entry(self) -> (K, V) { diff --git a/src/set.rs b/src/set.rs index 29fb64f0..aa4e7749 100644 --- a/src/set.rs +++ b/src/set.rs @@ -425,7 +425,7 @@ where /// /// Like `Vec::swap_remove`, the value is removed by swapping it with the /// last element of the set and popping it off. **This perturbs - /// the postion of what used to be the last element!** + /// the position of what used to be the last element!** /// /// Return `false` if `value` was not in the set. /// @@ -473,7 +473,7 @@ where /// /// Like `Vec::swap_remove`, the value is removed by swapping it with the /// last element of the set and popping it off. **This perturbs - /// the postion of what used to be the last element!** + /// the position of what used to be the last element!** /// /// Return `None` if `value` was not in the set. /// @@ -506,7 +506,7 @@ where /// /// Like `Vec::swap_remove`, the value is removed by swapping it with the /// last element of the set and popping it off. **This perturbs - /// the postion of what used to be the last element!** + /// the position of what used to be the last element!** /// /// Return `None` if `value` was not in the set. pub fn swap_remove_full(&mut self, value: &Q) -> Option<(usize, T)> @@ -622,7 +622,7 @@ impl IndexSet { /// /// Like `Vec::swap_remove`, the value is removed by swapping it with the /// last element of the set and popping it off. **This perturbs - /// the postion of what used to be the last element!** + /// the position of what used to be the last element!** /// /// Computes in **O(1)** time (average). pub fn swap_remove_index(&mut self, index: usize) -> Option { From 0dc0c7b73de03a56f68f4de1f5d8896985572454 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 4 Mar 2021 17:30:41 -0800 Subject: [PATCH 006/236] cargo fmt --- src/map.rs | 2 +- test-nostd/src/lib.rs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/map.rs b/src/map.rs index e16e064a..3bfaa150 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1665,7 +1665,7 @@ mod tests { let ptr = *e.key() as *const i32; assert_eq!(ptr, k1_ptr); assert_ne!(ptr, k2_ptr); - }, + } Entry::Vacant(_) => panic!(), } } diff --git a/test-nostd/src/lib.rs b/test-nostd/src/lib.rs index d54ade9d..0b57b092 100644 --- a/test-nostd/src/lib.rs +++ b/test-nostd/src/lib.rs @@ -11,7 +11,9 @@ use indexmap::IndexSet; struct BadHasher(u64); impl Hasher for BadHasher { - fn finish(&self) -> u64 { self.0 } + fn finish(&self) -> u64 { + self.0 + } fn write(&mut self, bytes: &[u8]) { for &byte in bytes { self.0 += byte as u64 From 6b54fded902f9d0d10a8a15ff3addf09eccd9380 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 4 Mar 2021 17:30:59 -0800 Subject: [PATCH 007/236] Release 1.6.2 --- Cargo.toml | 2 +- README.rst | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b9bf5498..d79a52e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "indexmap" edition = "2018" -version = "1.6.1" +version = "1.6.2" authors = [ "bluss", "Josh Stone " diff --git a/README.rst b/README.rst index 49a71073..1cf5368b 100644 --- a/README.rst +++ b/README.rst @@ -66,6 +66,17 @@ which is roughly: Recent Changes ============== +- 1.6.2 + + - Fixed to match ``std`` behavior, ``OccupiedEntry::key`` now references the + existing key in the map instead of the lookup key, by @cuviper in PR 170_. + + - The new ``Entry::or_insert_with_key`` matches Rust 1.50's ``Entry`` method, + passing ``&K`` to the callback to create a value, by @cuviper in PR 175_. + +.. _170: https://github.com/bluss/indexmap/pull/170 +.. _175: https://github.com/bluss/indexmap/pull/175 + - 1.6.1 - The new ``serde_seq`` module implements ``IndexMap`` serialization as a From a781b14684ab8d0ead03cb07ca2f83d64650b652 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Mon, 15 Mar 2021 11:20:27 -0700 Subject: [PATCH 008/236] Prefer copied() iterators instead of cloned() `Copied` is available since Rust 1.36 and Rayon 1.2. --- Cargo.toml | 2 +- benches/bench.rs | 4 ++-- src/map.rs | 6 +++--- src/rayon/map.rs | 6 +++--- src/rayon/set.rs | 2 +- src/set.rs | 4 ++-- tests/quick.rs | 12 ++++++------ 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d79a52e4..165b5270 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ bench = false autocfg = "1" [dependencies] serde = { version = "1.0", optional = true, default-features = false } -rayon = { version = "1.0", optional = true } +rayon = { version = "1.2", optional = true } [dependencies.hashbrown] version = "0.9.1" diff --git a/benches/bench.rs b/benches/bench.rs index 102cd49c..d6de602d 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -561,7 +561,7 @@ fn indexmap_merge_shuffle(b: &mut Bencher) { #[bench] fn swap_remove_indexmap_100_000(b: &mut Bencher) { let map = IMAP_100K.clone(); - let mut keys = Vec::from_iter(map.keys().cloned()); + let mut keys = Vec::from_iter(map.keys().copied()); let mut rng = small_rng(); keys.shuffle(&mut rng); @@ -578,7 +578,7 @@ fn swap_remove_indexmap_100_000(b: &mut Bencher) { #[bench] fn shift_remove_indexmap_100_000_few(b: &mut Bencher) { let map = IMAP_100K.clone(); - let mut keys = Vec::from_iter(map.keys().cloned()); + let mut keys = Vec::from_iter(map.keys().copied()); let mut rng = small_rng(); keys.shuffle(&mut rng); keys.truncate(50); diff --git a/src/map.rs b/src/map.rs index 3bfaa150..35087d67 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1674,7 +1674,7 @@ mod tests { fn keys() { let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; let map: IndexMap<_, _> = vec.into_iter().collect(); - let keys: Vec<_> = map.keys().cloned().collect(); + let keys: Vec<_> = map.keys().copied().collect(); assert_eq!(keys.len(), 3); assert!(keys.contains(&1)); assert!(keys.contains(&2)); @@ -1685,7 +1685,7 @@ mod tests { fn values() { let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; let map: IndexMap<_, _> = vec.into_iter().collect(); - let values: Vec<_> = map.values().cloned().collect(); + let values: Vec<_> = map.values().copied().collect(); assert_eq!(values.len(), 3); assert!(values.contains(&'a')); assert!(values.contains(&'b')); @@ -1699,7 +1699,7 @@ mod tests { for value in map.values_mut() { *value *= 2 } - let values: Vec<_> = map.values().cloned().collect(); + let values: Vec<_> = map.values().copied().collect(); assert_eq!(values.len(), 3); assert!(values.contains(&2)); assert!(values.contains(&4)); diff --git a/src/rayon/map.rs b/src/rayon/map.rs index ed4c74e4..6bc694b5 100644 --- a/src/rayon/map.rs +++ b/src/rayon/map.rs @@ -464,7 +464,7 @@ mod tests { fn keys() { let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; let map: IndexMap<_, _> = vec.into_par_iter().collect(); - let keys: Vec<_> = map.par_keys().cloned().collect(); + let keys: Vec<_> = map.par_keys().copied().collect(); assert_eq!(keys.len(), 3); assert!(keys.contains(&1)); assert!(keys.contains(&2)); @@ -475,7 +475,7 @@ mod tests { fn values() { let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; let map: IndexMap<_, _> = vec.into_par_iter().collect(); - let values: Vec<_> = map.par_values().cloned().collect(); + let values: Vec<_> = map.par_values().copied().collect(); assert_eq!(values.len(), 3); assert!(values.contains(&'a')); assert!(values.contains(&'b')); @@ -487,7 +487,7 @@ mod tests { let vec = vec![(1, 1), (2, 2), (3, 3)]; let mut map: IndexMap<_, _> = vec.into_par_iter().collect(); map.par_values_mut().for_each(|value| *value *= 2); - let values: Vec<_> = map.par_values().cloned().collect(); + let values: Vec<_> = map.par_values().copied().collect(); assert_eq!(values.len(), 3); assert!(values.contains(&2)); assert!(values.contains(&4)); diff --git a/src/rayon/set.rs b/src/rayon/set.rs index f6e08d48..f3e0da91 100644 --- a/src/rayon/set.rs +++ b/src/rayon/set.rs @@ -622,7 +622,7 @@ mod tests { I1: ParallelIterator, I2: Iterator, { - let v1: Vec<_> = iter1.cloned().collect(); + let v1: Vec<_> = iter1.copied().collect(); let v2: Vec<_> = iter2.collect(); assert_eq!(v1, v2); } diff --git a/src/set.rs b/src/set.rs index aa4e7749..9816fba5 100644 --- a/src/set.rs +++ b/src/set.rs @@ -840,7 +840,7 @@ where S: BuildHasher, { fn extend>(&mut self, iterable: I) { - let iter = iterable.into_iter().cloned(); // FIXME: use `copied` in Rust 1.36 + let iter = iterable.into_iter().copied(); self.extend(iter); } } @@ -1560,7 +1560,7 @@ mod tests { I1: Iterator, I2: Iterator, { - assert!(iter1.cloned().eq(iter2)); + assert!(iter1.copied().eq(iter2)); } let set_a: IndexSet<_> = (0..3).collect(); diff --git a/tests/quick.rs b/tests/quick.rs index 10e11b52..848e14fc 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -30,7 +30,7 @@ where I: IntoIterator, T: Copy + Hash + Eq, { - iter.into_iter().cloned().collect() + iter.into_iter().copied().collect() } fn indexmap<'a, T: 'a, I>(iter: I) -> IndexMap @@ -38,7 +38,7 @@ where I: IntoIterator, T: Copy + Hash + Eq, { - IndexMap::from_iter(iter.into_iter().cloned().map(|k| (k, ()))) + IndexMap::from_iter(iter.into_iter().copied().map(|k| (k, ()))) } quickcheck! { @@ -123,7 +123,7 @@ quickcheck! { // First see if `Vec::drain` is happy with this range. let result = std::panic::catch_unwind(|| { - let mut keys: Vec = map.keys().cloned().collect(); + let mut keys: Vec = map.keys().copied().collect(); keys.drain(range); keys }); @@ -155,7 +155,7 @@ quickcheck! { let mut iter = map.keys(); for &key in insert.iter().unique() { if elements.contains(&key) { - assert_eq!(Some(key), iter.next().cloned()); + assert_eq!(Some(&key), iter.next()); } } @@ -165,7 +165,7 @@ quickcheck! { fn indexing(insert: Vec) -> bool { let mut map: IndexMap<_, _> = insert.into_iter().map(|x| (x, x)).collect(); - let set: IndexSet<_> = map.keys().cloned().collect(); + let set: IndexSet<_> = map.keys().copied().collect(); assert_eq!(map.len(), set.len()); for (i, &key) in set.iter().enumerate() { @@ -295,7 +295,7 @@ quickcheck! { let mut reference = HashMap::new(); do_ops(&ops, &mut map, &mut reference); let mut visit = IndexMap::new(); - let keys = Vec::from_iter(map.keys().cloned()); + let keys = Vec::from_iter(map.keys().copied()); for (k, v) in keys.iter().zip(map.values_mut()) { assert_eq!(&reference[k], v); assert!(!visit.contains_key(k)); From 789305dbe84bb33f64d4db1ed0e8c4db5841ebe3 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Mon, 15 Mar 2021 11:29:51 -0700 Subject: [PATCH 009/236] Use modern tool lints for clippy --- src/set.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/set.rs b/src/set.rs index aa4e7749..57eb6da0 100644 --- a/src/set.rs +++ b/src/set.rs @@ -1612,8 +1612,7 @@ mod tests { let set_c: IndexSet<_> = (0..6).collect(); let set_d: IndexSet<_> = (3..9).rev().collect(); - // FIXME: #[allow(clippy::eq_op)] in Rust 1.31 - #[cfg_attr(feature = "cargo-clippy", allow(renamed_and_removed_lints, eq_op))] + #[allow(clippy::eq_op)] { assert_eq!(&set_a & &set_a, set_a); assert_eq!(&set_a | &set_a, set_a); From 9923b34875ca84045682658400f795286ffcb979 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Sun, 25 Apr 2021 01:47:46 +0900 Subject: [PATCH 010/236] Fix typo in map.rs --- src/map.rs | 2 +- src/rayon/map.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/map.rs b/src/map.rs index 6829ef94..81afdb1d 100644 --- a/src/map.rs +++ b/src/map.rs @@ -237,7 +237,7 @@ impl IndexMap { } } - /// Return an iterator over mutable references to the the values of the map, + /// Return an iterator over mutable references to the values of the map, /// in their order pub fn values_mut(&mut self) -> ValuesMut<'_, K, V> { ValuesMut { diff --git a/src/rayon/map.rs b/src/rayon/map.rs index ed4c74e4..26891422 100644 --- a/src/rayon/map.rs +++ b/src/rayon/map.rs @@ -275,7 +275,7 @@ where K: Send, V: Send, { - /// Return a parallel iterator over mutable references to the the values of the map + /// Return a parallel iterator over mutable references to the values of the map /// /// While parallel iterators can process items in any order, their relative order /// in the map is still preserved for operations like `reduce` and `collect`. From a5893fedd3fe67dee5908da7b2fa237f64e821a8 Mon Sep 17 00:00:00 2001 From: Balthazar Potet Date: Thu, 6 May 2021 16:39:07 +0200 Subject: [PATCH 011/236] Document complexity of get_index_of --- src/map.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/map.rs b/src/map.rs index 128cf9da..a482a399 100644 --- a/src/map.rs +++ b/src/map.rs @@ -424,6 +424,8 @@ where } /// Return item index, if it exists in the map + /// + /// Computes in **O(1)** time (average). pub fn get_index_of(&self, key: &Q) -> Option where Q: Hash + Equivalent, From 5f238f76c539dcd18e7aea099045feebc650a86a Mon Sep 17 00:00:00 2001 From: bjorn3 Date: Tue, 11 May 2021 13:14:27 +0200 Subject: [PATCH 012/236] Simplify logic around `#[cfg(has_std)]` --- src/lib.rs | 5 ----- src/rayon/mod.rs | 4 ---- 2 files changed, 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1a58a830..0ccdb0ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,19 +77,14 @@ //! //! [def]: map/struct.IndexMap.html#impl-Default -#[cfg(not(has_std))] extern crate alloc; #[cfg(has_std)] #[macro_use] extern crate std; -#[cfg(not(has_std))] use alloc::vec::{self, Vec}; -#[cfg(has_std)] -use std::vec::{self, Vec}; - #[macro_use] mod macros; mod equivalent; diff --git a/src/rayon/mod.rs b/src/rayon/mod.rs index 57c810be..757a54dd 100644 --- a/src/rayon/mod.rs +++ b/src/rayon/mod.rs @@ -1,11 +1,7 @@ use rayon::prelude::*; -#[cfg(not(has_std))] use alloc::collections::LinkedList; -#[cfg(has_std)] -use std::collections::LinkedList; - use crate::vec::Vec; // generate `ParallelIterator` methods by just forwarding to the underlying From 7819c6ddd733c8c527c1c26d2fd2bc3f506226b0 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 2 Jun 2021 18:07:42 -0700 Subject: [PATCH 013/236] Update to hashbrown 0.11 (MSRV 1.49) --- .github/workflows/ci.yml | 4 ++-- Cargo.toml | 4 ++-- README.rst | 10 ++++++++-- src/lib.rs | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b7a5e6dd..a399c1f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: include: - - rust: 1.36.0 # MSRV + - rust: 1.49.0 # MSRV features: - rust: stable features: serde @@ -57,7 +57,7 @@ jobs: strategy: matrix: include: - - rust: 1.36.0 + - rust: 1.49.0 target: thumbv6m-none-eabi - rust: stable target: thumbv6m-none-eabi diff --git a/Cargo.toml b/Cargo.toml index 165b5270..d099405d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "indexmap" edition = "2018" -version = "1.6.2" +version = "1.7.0" authors = [ "bluss", "Josh Stone " @@ -37,7 +37,7 @@ serde = { version = "1.0", optional = true, default-features = false } rayon = { version = "1.2", optional = true } [dependencies.hashbrown] -version = "0.9.1" +version = "0.11" default-features = false features = ["raw"] diff --git a/README.rst b/README.rst index 1cf5368b..e4617f8e 100644 --- a/README.rst +++ b/README.rst @@ -12,8 +12,8 @@ indexmap .. |docs| image:: https://docs.rs/indexmap/badge.svg .. _docs: https://docs.rs/indexmap -.. |rustc| image:: https://img.shields.io/badge/rust-1.36%2B-orange.svg -.. _rustc: https://img.shields.io/badge/rust-1.36%2B-orange.svg +.. |rustc| image:: https://img.shields.io/badge/rust-1.49%2B-orange.svg +.. _rustc: https://img.shields.io/badge/rust-1.49%2B-orange.svg A pure-Rust hash table which preserves (in a limited sense) insertion order. @@ -66,6 +66,12 @@ which is roughly: Recent Changes ============== +- 1.7.0 + + - **MSRV**: Rust 1.49 or later is now required. + + - The ``hashbrown`` dependency has been updated to version 0.11. + - 1.6.2 - Fixed to match ``std`` behavior, ``OccupiedEntry::key`` now references the diff --git a/src/lib.rs b/src/lib.rs index 0ccdb0ff..aa419da3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,7 +53,7 @@ //! //! ### Rust Version //! -//! This version of indexmap requires Rust 1.36 or later. +//! This version of indexmap requires Rust 1.49 or later. //! //! The indexmap 1.x release series will use a carefully considered version //! upgrade policy, where in a later 1.x version, we will raise the minimum From f75d5bfef7bd1025e8ed62ed9a4756117b87f4e6 Mon Sep 17 00:00:00 2001 From: Dirk Stolle Date: Thu, 1 Jul 2021 23:09:57 +0200 Subject: [PATCH 014/236] fix typos --- src/mutable_keys.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mutable_keys.rs b/src/mutable_keys.rs index 0688441e..35a90c47 100644 --- a/src/mutable_keys.rs +++ b/src/mutable_keys.rs @@ -8,10 +8,10 @@ pub struct PrivateMarker {} /// /// These methods expose `&mut K`, mutable references to the key as it is stored /// in the map. -/// You are allowed to modify the keys in the hashmap **if the modifcation +/// You are allowed to modify the keys in the hashmap **if the modification /// does not change the key’s hash and equality**. /// -/// If keys are modified erronously, you can no longer look them up. +/// If keys are modified erroneously, you can no longer look them up. /// This is sound (memory safe) but a logical error hazard (just like /// implementing PartialEq, Eq, or Hash incorrectly would be). /// From 49d430808ced6d1824ec15d621acd6b0e8367c19 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Sat, 31 Jul 2021 03:09:00 +0900 Subject: [PATCH 015/236] Add IndexMap::into_{keys,values} --- src/lib.rs | 3 ++ src/map.rs | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index aa419da3..489e5486 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -162,6 +162,9 @@ impl Bucket { fn key(self) -> K { self.key } + fn value(self) -> V { + self.value + } fn key_value(self) -> (K, V) { (self.key, self.value) } diff --git a/src/map.rs b/src/map.rs index a482a399..aa8d06a0 100644 --- a/src/map.rs +++ b/src/map.rs @@ -230,6 +230,13 @@ impl IndexMap { } } + /// Return an owning iterator over the keys of the map, in their order + pub fn into_keys(self) -> IntoKeys { + IntoKeys { + iter: self.into_entries().into_iter(), + } + } + /// Return an iterator over the values of the map, in their order pub fn values(&self) -> Values<'_, K, V> { Values { @@ -245,6 +252,13 @@ impl IndexMap { } } + /// Return an owning iterator over the values of the map, in their order + pub fn into_values(self) -> IntoValues { + IntoValues { + iter: self.into_entries().into_iter(), + } + } + /// Remove all key-value pairs in the map, while preserving its capacity. /// /// Computes in **O(n)** time. @@ -825,6 +839,42 @@ impl fmt::Debug for Keys<'_, K, V> { } } +/// An owning iterator over the keys of a `IndexMap`. +/// +/// This `struct` is created by the [`into_keys`] method on [`IndexMap`]. +/// See its documentation for more. +/// +/// [`IndexMap`]: struct.IndexMap.html +/// [`into_keys`]: struct.IndexMap.html#method.into_keys +pub struct IntoKeys { + iter: vec::IntoIter>, +} + +impl Iterator for IntoKeys { + type Item = K; + + iterator_methods!(Bucket::key); +} + +impl DoubleEndedIterator for IntoKeys { + fn next_back(&mut self) -> Option { + self.iter.next_back().map(Bucket::key) + } +} + +impl ExactSizeIterator for IntoKeys { + fn len(&self) -> usize { + self.iter.len() + } +} + +impl fmt::Debug for IntoKeys { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let iter = self.iter.as_slice().iter().map(Bucket::key_ref); + f.debug_list().entries(iter).finish() + } +} + /// An iterator over the values of a `IndexMap`. /// /// This `struct` is created by the [`values`] method on [`IndexMap`]. See its @@ -898,6 +948,42 @@ impl ExactSizeIterator for ValuesMut<'_, K, V> { } } +/// An owning iterator over the values of a `IndexMap`. +/// +/// This `struct` is created by the [`into_values`] method on [`IndexMap`]. +/// See its documentation for more. +/// +/// [`IndexMap`]: struct.IndexMap.html +/// [`into_values`]: struct.IndexMap.html#method.into_values +pub struct IntoValues { + iter: vec::IntoIter>, +} + +impl Iterator for IntoValues { + type Item = V; + + iterator_methods!(Bucket::value); +} + +impl DoubleEndedIterator for IntoValues { + fn next_back(&mut self) -> Option { + self.iter.next_back().map(Bucket::value) + } +} + +impl ExactSizeIterator for IntoValues { + fn len(&self) -> usize { + self.iter.len() + } +} + +impl fmt::Debug for IntoValues { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let iter = self.iter.as_slice().iter().map(Bucket::value_ref); + f.debug_list().entries(iter).finish() + } +} + /// An iterator over the entries of a `IndexMap`. /// /// This `struct` is created by the [`iter`] method on [`IndexMap`]. See its @@ -1683,6 +1769,17 @@ mod tests { assert!(keys.contains(&3)); } + #[test] + fn into_keys() { + let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; + let map: IndexMap<_, _> = vec.into_iter().collect(); + let keys: Vec = map.into_keys().collect(); + assert_eq!(keys.len(), 3); + assert!(keys.contains(&1)); + assert!(keys.contains(&2)); + assert!(keys.contains(&3)); + } + #[test] fn values() { let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; @@ -1707,4 +1804,15 @@ mod tests { assert!(values.contains(&4)); assert!(values.contains(&6)); } + + #[test] + fn into_values() { + let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; + let map: IndexMap<_, _> = vec.into_iter().collect(); + let values: Vec = map.into_values().collect(); + assert_eq!(values.len(), 3); + assert!(values.contains(&'a')); + assert!(values.contains(&'b')); + assert!(values.contains(&'c')); + } } From eed52fb5e2d90c25ba25b1fe071f8d2091305201 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 30 Jul 2021 15:42:04 -0700 Subject: [PATCH 016/236] Use double_ended_iterator_methods! --- src/map.rs | 32 ++++++++------------------------ src/set.rs | 8 ++------ 2 files changed, 10 insertions(+), 30 deletions(-) diff --git a/src/map.rs b/src/map.rs index aa8d06a0..9b833c35 100644 --- a/src/map.rs +++ b/src/map.rs @@ -813,9 +813,7 @@ impl<'a, K, V> Iterator for Keys<'a, K, V> { } impl DoubleEndedIterator for Keys<'_, K, V> { - fn next_back(&mut self) -> Option { - self.iter.next_back().map(Bucket::key_ref) - } + double_ended_iterator_methods!(Bucket::key_ref); } impl ExactSizeIterator for Keys<'_, K, V> { @@ -857,9 +855,7 @@ impl Iterator for IntoKeys { } impl DoubleEndedIterator for IntoKeys { - fn next_back(&mut self) -> Option { - self.iter.next_back().map(Bucket::key) - } + double_ended_iterator_methods!(Bucket::key); } impl ExactSizeIterator for IntoKeys { @@ -893,9 +889,7 @@ impl<'a, K, V> Iterator for Values<'a, K, V> { } impl DoubleEndedIterator for Values<'_, K, V> { - fn next_back(&mut self) -> Option { - self.iter.next_back().map(Bucket::value_ref) - } + double_ended_iterator_methods!(Bucket::value_ref); } impl ExactSizeIterator for Values<'_, K, V> { @@ -937,9 +931,7 @@ impl<'a, K, V> Iterator for ValuesMut<'a, K, V> { } impl DoubleEndedIterator for ValuesMut<'_, K, V> { - fn next_back(&mut self) -> Option { - self.iter.next_back().map(Bucket::value_mut) - } + double_ended_iterator_methods!(Bucket::value_mut); } impl ExactSizeIterator for ValuesMut<'_, K, V> { @@ -966,9 +958,7 @@ impl Iterator for IntoValues { } impl DoubleEndedIterator for IntoValues { - fn next_back(&mut self) -> Option { - self.iter.next_back().map(Bucket::value) - } + double_ended_iterator_methods!(Bucket::value); } impl ExactSizeIterator for IntoValues { @@ -1002,9 +992,7 @@ impl<'a, K, V> Iterator for Iter<'a, K, V> { } impl DoubleEndedIterator for Iter<'_, K, V> { - fn next_back(&mut self) -> Option { - self.iter.next_back().map(Bucket::refs) - } + double_ended_iterator_methods!(Bucket::refs); } impl ExactSizeIterator for Iter<'_, K, V> { @@ -1046,9 +1034,7 @@ impl<'a, K, V> Iterator for IterMut<'a, K, V> { } impl DoubleEndedIterator for IterMut<'_, K, V> { - fn next_back(&mut self) -> Option { - self.iter.next_back().map(Bucket::ref_mut) - } + double_ended_iterator_methods!(Bucket::ref_mut); } impl ExactSizeIterator for IterMut<'_, K, V> { @@ -1075,9 +1061,7 @@ impl Iterator for IntoIter { } impl DoubleEndedIterator for IntoIter { - fn next_back(&mut self) -> Option { - self.iter.next_back().map(Bucket::key_value) - } + double_ended_iterator_methods!(Bucket::key_value); } impl ExactSizeIterator for IntoIter { diff --git a/src/set.rs b/src/set.rs index 66c3da4d..f7f3232e 100644 --- a/src/set.rs +++ b/src/set.rs @@ -708,9 +708,7 @@ impl Iterator for IntoIter { } impl DoubleEndedIterator for IntoIter { - fn next_back(&mut self) -> Option { - self.iter.next_back().map(Bucket::key) - } + double_ended_iterator_methods!(Bucket::key); } impl ExactSizeIterator for IntoIter { @@ -744,9 +742,7 @@ impl<'a, T> Iterator for Iter<'a, T> { } impl DoubleEndedIterator for Iter<'_, T> { - fn next_back(&mut self) -> Option { - self.iter.next_back().map(Bucket::key_ref) - } + double_ended_iterator_methods!(Bucket::key_ref); } impl ExactSizeIterator for Iter<'_, T> { From d5c6269dadba2025022c653acf1d0cade3039113 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 30 Jul 2021 15:48:39 -0700 Subject: [PATCH 017/236] Specialize nth_back just like nth --- src/macros.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/macros.rs b/src/macros.rs index c4d84217..a7b33508 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -116,5 +116,9 @@ macro_rules! double_ended_iterator_methods { fn next_back(&mut self) -> Option { self.iter.next_back().map($map_elt) } + + fn nth_back(&mut self, n: usize) -> Option { + self.iter.nth_back(n).map($map_elt) + } }; } From 306d6b11603cac5dcb2552941ff7465ef6756a13 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 30 Jul 2021 15:48:43 -0700 Subject: [PATCH 018/236] Add rfold for iterators built on Chain --- src/set.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/set.rs b/src/set.rs index f7f3232e..f559999a 100644 --- a/src/set.rs +++ b/src/set.rs @@ -1083,6 +1083,13 @@ where fn next_back(&mut self) -> Option { self.iter.next_back() } + + fn rfold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.iter.rfold(init, f) + } } impl Clone for SymmetricDifference<'_, T, S1, S2> { @@ -1146,6 +1153,13 @@ where fn next_back(&mut self) -> Option { self.iter.next_back() } + + fn rfold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.iter.rfold(init, f) + } } impl Clone for Union<'_, T, S> { From 3a44f893241a52f3fd71b6aac520017e8a2f6c29 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 30 Jul 2021 16:07:38 -0700 Subject: [PATCH 019/236] impl ExactSizeIterator for Drain --- src/map.rs | 6 ++++++ src/set.rs | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/map.rs b/src/map.rs index 9b833c35..fad30cf6 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1098,6 +1098,12 @@ impl DoubleEndedIterator for Drain<'_, K, V> { double_ended_iterator_methods!(Bucket::key_value); } +impl ExactSizeIterator for Drain<'_, K, V> { + fn len(&self) -> usize { + self.iter.len() + } +} + impl<'a, K, V, S> IntoIterator for &'a IndexMap { type Item = (&'a K, &'a V); type IntoIter = Iter<'a, K, V>; diff --git a/src/set.rs b/src/set.rs index f559999a..404af274 100644 --- a/src/set.rs +++ b/src/set.rs @@ -786,6 +786,12 @@ impl DoubleEndedIterator for Drain<'_, T> { double_ended_iterator_methods!(Bucket::key); } +impl ExactSizeIterator for Drain<'_, T> { + fn len(&self) -> usize { + self.iter.len() + } +} + impl<'a, T, S> IntoIterator for &'a IndexSet { type Item = &'a T; type IntoIter = Iter<'a, T>; From 7423210e0e0575c8df578b51d95c17bb5630f5ed Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 30 Jul 2021 16:08:29 -0700 Subject: [PATCH 020/236] impl FusedIterator for all iterators --- src/map.rs | 20 +++++++++++++++++++- src/set.rs | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/map.rs b/src/map.rs index fad30cf6..3d0536a1 100644 --- a/src/map.rs +++ b/src/map.rs @@ -12,7 +12,7 @@ use crate::vec::{self, Vec}; use ::core::cmp::Ordering; use ::core::fmt; use ::core::hash::{BuildHasher, Hash, Hasher}; -use ::core::iter::FromIterator; +use ::core::iter::{FromIterator, FusedIterator}; use ::core::ops::{Index, IndexMut, RangeBounds}; use ::core::slice::{Iter as SliceIter, IterMut as SliceIterMut}; @@ -822,6 +822,8 @@ impl ExactSizeIterator for Keys<'_, K, V> { } } +impl FusedIterator for Keys<'_, K, V> {} + // FIXME(#26925) Remove in favor of `#[derive(Clone)]` impl Clone for Keys<'_, K, V> { fn clone(&self) -> Self { @@ -864,6 +866,8 @@ impl ExactSizeIterator for IntoKeys { } } +impl FusedIterator for IntoKeys {} + impl fmt::Debug for IntoKeys { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let iter = self.iter.as_slice().iter().map(Bucket::key_ref); @@ -898,6 +902,8 @@ impl ExactSizeIterator for Values<'_, K, V> { } } +impl FusedIterator for Values<'_, K, V> {} + // FIXME(#26925) Remove in favor of `#[derive(Clone)]` impl Clone for Values<'_, K, V> { fn clone(&self) -> Self { @@ -940,6 +946,8 @@ impl ExactSizeIterator for ValuesMut<'_, K, V> { } } +impl FusedIterator for ValuesMut<'_, K, V> {} + /// An owning iterator over the values of a `IndexMap`. /// /// This `struct` is created by the [`into_values`] method on [`IndexMap`]. @@ -967,6 +975,8 @@ impl ExactSizeIterator for IntoValues { } } +impl FusedIterator for IntoValues {} + impl fmt::Debug for IntoValues { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let iter = self.iter.as_slice().iter().map(Bucket::value_ref); @@ -1001,6 +1011,8 @@ impl ExactSizeIterator for Iter<'_, K, V> { } } +impl FusedIterator for Iter<'_, K, V> {} + // FIXME(#26925) Remove in favor of `#[derive(Clone)]` impl Clone for Iter<'_, K, V> { fn clone(&self) -> Self { @@ -1043,6 +1055,8 @@ impl ExactSizeIterator for IterMut<'_, K, V> { } } +impl FusedIterator for IterMut<'_, K, V> {} + /// An owning iterator over the entries of a `IndexMap`. /// /// This `struct` is created by the [`into_iter`] method on [`IndexMap`] @@ -1070,6 +1084,8 @@ impl ExactSizeIterator for IntoIter { } } +impl FusedIterator for IntoIter {} + impl fmt::Debug for IntoIter { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let iter = self.iter.as_slice().iter().map(Bucket::refs); @@ -1104,6 +1120,8 @@ impl ExactSizeIterator for Drain<'_, K, V> { } } +impl FusedIterator for Drain<'_, K, V> {} + impl<'a, K, V, S> IntoIterator for &'a IndexMap { type Item = (&'a K, &'a V); type IntoIter = Iter<'a, K, V>; diff --git a/src/set.rs b/src/set.rs index 404af274..e17163e7 100644 --- a/src/set.rs +++ b/src/set.rs @@ -10,7 +10,7 @@ use crate::vec::{self, Vec}; use core::cmp::Ordering; use core::fmt; use core::hash::{BuildHasher, Hash}; -use core::iter::{Chain, FromIterator}; +use core::iter::{Chain, FromIterator, FusedIterator}; use core::ops::{BitAnd, BitOr, BitXor, Index, RangeBounds, Sub}; use core::slice; @@ -717,6 +717,8 @@ impl ExactSizeIterator for IntoIter { } } +impl FusedIterator for IntoIter {} + impl fmt::Debug for IntoIter { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let iter = self.iter.as_slice().iter().map(Bucket::key_ref); @@ -751,6 +753,8 @@ impl ExactSizeIterator for Iter<'_, T> { } } +impl FusedIterator for Iter<'_, T> {} + impl Clone for Iter<'_, T> { fn clone(&self) -> Self { Iter { @@ -792,6 +796,8 @@ impl ExactSizeIterator for Drain<'_, T> { } } +impl FusedIterator for Drain<'_, T> {} + impl<'a, T, S> IntoIterator for &'a IndexSet { type Item = &'a T; type IntoIter = Iter<'a, T>; @@ -959,6 +965,13 @@ where } } +impl FusedIterator for Difference<'_, T, S> +where + T: Eq + Hash, + S: BuildHasher, +{ +} + impl Clone for Difference<'_, T, S> { fn clone(&self) -> Self { Difference { @@ -1026,6 +1039,13 @@ where } } +impl FusedIterator for Intersection<'_, T, S> +where + T: Eq + Hash, + S: BuildHasher, +{ +} + impl Clone for Intersection<'_, T, S> { fn clone(&self) -> Self { Intersection { @@ -1098,6 +1118,14 @@ where } } +impl FusedIterator for SymmetricDifference<'_, T, S1, S2> +where + T: Eq + Hash, + S1: BuildHasher, + S2: BuildHasher, +{ +} + impl Clone for SymmetricDifference<'_, T, S1, S2> { fn clone(&self) -> Self { SymmetricDifference { @@ -1168,6 +1196,13 @@ where } } +impl FusedIterator for Union<'_, T, S> +where + T: Eq + Hash, + S: BuildHasher, +{ +} + impl Clone for Union<'_, T, S> { fn clone(&self) -> Self { Union { From 2b9cbea4a185f9bc4ca89b7c7ff005d5f4f030be Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 30 Jul 2021 16:23:23 -0700 Subject: [PATCH 021/236] impl Debug for Drain --- src/map.rs | 7 +++++++ src/set.rs | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/map.rs b/src/map.rs index 3d0536a1..a2d8122d 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1122,6 +1122,13 @@ impl ExactSizeIterator for Drain<'_, K, V> { impl FusedIterator for Drain<'_, K, V> {} +impl fmt::Debug for Drain<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let iter = self.iter.as_slice().iter().map(Bucket::refs); + f.debug_list().entries(iter).finish() + } +} + impl<'a, K, V, S> IntoIterator for &'a IndexMap { type Item = (&'a K, &'a V); type IntoIter = Iter<'a, K, V>; diff --git a/src/set.rs b/src/set.rs index e17163e7..134c9045 100644 --- a/src/set.rs +++ b/src/set.rs @@ -798,6 +798,13 @@ impl ExactSizeIterator for Drain<'_, T> { impl FusedIterator for Drain<'_, T> {} +impl fmt::Debug for Drain<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let iter = self.iter.as_slice().iter().map(Bucket::key_ref); + f.debug_list().entries(iter).finish() + } +} + impl<'a, T, S> IntoIterator for &'a IndexSet { type Item = &'a T; type IntoIter = Iter<'a, T>; From 19789dcb537964e47cc88646a8eb9739d1064a25 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 30 Jul 2021 16:25:08 -0700 Subject: [PATCH 022/236] impl Debug for ParIterMut --- src/map.rs | 2 ++ src/rayon/map.rs | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/src/map.rs b/src/map.rs index a2d8122d..4beb5f44 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1057,6 +1057,8 @@ impl ExactSizeIterator for IterMut<'_, K, V> { impl FusedIterator for IterMut<'_, K, V> {} +// TODO: `impl Debug for IterMut` once we have MSRV 1.53 for `slice::IterMut::as_slice` + /// An owning iterator over the entries of a `IndexMap`. /// /// This `struct` is created by the [`into_iter`] method on [`IndexMap`] diff --git a/src/rayon/map.rs b/src/rayon/map.rs index ed2da3ef..1bfce60c 100644 --- a/src/rayon/map.rs +++ b/src/rayon/map.rs @@ -139,6 +139,13 @@ pub struct ParIterMut<'a, K, V> { entries: &'a mut [Bucket], } +impl fmt::Debug for ParIterMut<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let iter = self.entries.iter().map(Bucket::refs); + f.debug_list().entries(iter).finish() + } +} + impl<'a, K: Sync + Send, V: Send> ParallelIterator for ParIterMut<'a, K, V> { type Item = (&'a K, &'a mut V); From e594967b47b9f1dedbf8b9693b61a38f2994aa69 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 30 Jul 2021 16:25:23 -0700 Subject: [PATCH 023/236] impl Debug for ParValuesMut --- src/map.rs | 2 ++ src/rayon/map.rs | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/src/map.rs b/src/map.rs index 4beb5f44..3a43a79b 100644 --- a/src/map.rs +++ b/src/map.rs @@ -948,6 +948,8 @@ impl ExactSizeIterator for ValuesMut<'_, K, V> { impl FusedIterator for ValuesMut<'_, K, V> {} +// TODO: `impl Debug for ValuesMut` once we have MSRV 1.53 for `slice::IterMut::as_slice` + /// An owning iterator over the values of a `IndexMap`. /// /// This `struct` is created by the [`into_values`] method on [`IndexMap`]. diff --git a/src/rayon/map.rs b/src/rayon/map.rs index 1bfce60c..bedad3c4 100644 --- a/src/rayon/map.rs +++ b/src/rayon/map.rs @@ -346,6 +346,13 @@ pub struct ParValuesMut<'a, K, V> { entries: &'a mut [Bucket], } +impl fmt::Debug for ParValuesMut<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let iter = self.entries.iter().map(Bucket::value_ref); + f.debug_list().entries(iter).finish() + } +} + impl<'a, K: Send, V: Send> ParallelIterator for ParValuesMut<'a, K, V> { type Item = &'a mut V; From 1426285755b25300f533aa0715b6618f9109a002 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 30 Jul 2021 16:53:20 -0700 Subject: [PATCH 024/236] impl ParallelDrainRange --- Cargo.toml | 2 +- src/map.rs | 4 ++-- src/map/core.rs | 13 +++++++++++++ src/rayon/map.rs | 38 ++++++++++++++++++++++++++++++++++++++ src/rayon/set.rs | 37 +++++++++++++++++++++++++++++++++++++ src/set.rs | 4 ++-- 6 files changed, 93 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d099405d..0a870468 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ bench = false autocfg = "1" [dependencies] serde = { version = "1.0", optional = true, default-features = false } -rayon = { version = "1.2", optional = true } +rayon = { version = "1.4.1", optional = true } [dependencies.hashbrown] version = "0.11" diff --git a/src/map.rs b/src/map.rs index aa8d06a0..5519aae4 100644 --- a/src/map.rs +++ b/src/map.rs @@ -69,12 +69,12 @@ pub use self::core::{Entry, OccupiedEntry, VacantEntry}; /// ``` #[cfg(has_std)] pub struct IndexMap { - core: IndexMapCore, + pub(crate) core: IndexMapCore, hash_builder: S, } #[cfg(not(has_std))] pub struct IndexMap { - core: IndexMapCore, + pub(crate) core: IndexMapCore, hash_builder: S, } diff --git a/src/map/core.rs b/src/map/core.rs index c4e725c9..87b81f8e 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -166,6 +166,19 @@ impl IndexMapCore { self.entries.drain(range) } + #[cfg(feature = "rayon")] + pub(crate) fn par_drain(&mut self, range: R) -> rayon::vec::Drain<'_, Bucket> + where + K: Send, + V: Send, + R: RangeBounds, + { + use rayon::iter::ParallelDrainRange; + let range = simplify_range(range, self.entries.len()); + self.erase_indices(range.start, range.end); + self.entries.par_drain(range) + } + pub(crate) fn split_off(&mut self, at: usize) -> Self { assert!(at <= self.entries.len()); self.erase_indices(at, self.entries.len()); diff --git a/src/rayon/map.rs b/src/rayon/map.rs index ed2da3ef..41c071c6 100644 --- a/src/rayon/map.rs +++ b/src/rayon/map.rs @@ -13,6 +13,7 @@ use crate::vec::Vec; use core::cmp::Ordering; use core::fmt; use core::hash::{BuildHasher, Hash}; +use core::ops::RangeBounds; use crate::Bucket; use crate::Entries; @@ -149,6 +150,43 @@ impl IndexedParallelIterator for ParIterMut<'_, K, V> { indexed_parallel_iterator_methods!(Bucket::ref_mut); } +/// Requires crate feature `"rayon"`. +impl<'a, K, V, S> ParallelDrainRange for &'a mut IndexMap +where + K: Send, + V: Send, +{ + type Item = (K, V); + type Iter = ParDrain<'a, K, V>; + + fn par_drain>(self, range: R) -> Self::Iter { + ParDrain { + entries: self.core.par_drain(range), + } + } +} + +/// A parallel draining iterator over the entries of a `IndexMap`. +/// +/// This `struct` is created by the [`par_drain`] method on [`IndexMap`] +/// (provided by rayon's `ParallelDrainRange` trait). See its documentation for more. +/// +/// [`par_drain`]: ../struct.IndexMap.html#method.par_drain +/// [`IndexMap`]: ../struct.IndexMap.html +pub struct ParDrain<'a, K: Send, V: Send> { + entries: rayon::vec::Drain<'a, Bucket>, +} + +impl ParallelIterator for ParDrain<'_, K, V> { + type Item = (K, V); + + parallel_iterator_methods!(Bucket::key_value); +} + +impl IndexedParallelIterator for ParDrain<'_, K, V> { + indexed_parallel_iterator_methods!(Bucket::key_value); +} + /// Parallel iterator methods and other parallel methods. /// /// The following methods **require crate feature `"rayon"`**. diff --git a/src/rayon/set.rs b/src/rayon/set.rs index f3e0da91..1ca0945d 100644 --- a/src/rayon/set.rs +++ b/src/rayon/set.rs @@ -13,6 +13,7 @@ use crate::vec::Vec; use core::cmp::Ordering; use core::fmt; use core::hash::{BuildHasher, Hash}; +use core::ops::RangeBounds; use crate::Entries; use crate::IndexSet; @@ -111,6 +112,42 @@ impl IndexedParallelIterator for ParIter<'_, T> { indexed_parallel_iterator_methods!(Bucket::key_ref); } +/// Requires crate feature `"rayon"`. +impl<'a, T, S> ParallelDrainRange for &'a mut IndexSet +where + T: Send, +{ + type Item = T; + type Iter = ParDrain<'a, T>; + + fn par_drain>(self, range: R) -> Self::Iter { + ParDrain { + entries: self.map.core.par_drain(range), + } + } +} + +/// A parallel draining iterator over the items of a `IndexSet`. +/// +/// This `struct` is created by the [`par_drain`] method on [`IndexSet`] +/// (provided by rayon's `ParallelDrainRange` trait). See its documentation for more. +/// +/// [`par_drain`]: ../struct.IndexSet.html#method.par_drain +/// [`IndexSet`]: ../struct.IndexSet.html +pub struct ParDrain<'a, T: Send> { + entries: rayon::vec::Drain<'a, Bucket>, +} + +impl ParallelIterator for ParDrain<'_, T> { + type Item = T; + + parallel_iterator_methods!(Bucket::key); +} + +impl IndexedParallelIterator for ParDrain<'_, T> { + indexed_parallel_iterator_methods!(Bucket::key); +} + /// Parallel iterator methods and other parallel methods. /// /// The following methods **require crate feature `"rayon"`**. diff --git a/src/set.rs b/src/set.rs index 66c3da4d..8036c434 100644 --- a/src/set.rs +++ b/src/set.rs @@ -61,11 +61,11 @@ type Bucket = super::Bucket; /// ``` #[cfg(has_std)] pub struct IndexSet { - map: IndexMap, + pub(crate) map: IndexMap, } #[cfg(not(has_std))] pub struct IndexSet { - map: IndexMap, + pub(crate) map: IndexMap, } impl Clone for IndexSet From 68da212e6f35fe811cf0c18207bd253ff4d0e176 Mon Sep 17 00:00:00 2001 From: Max Willsey Date: Thu, 16 Sep 2021 09:53:27 -0700 Subject: [PATCH 025/236] Make with_hasher const --- src/map.rs | 12 ++++++------ src/map/core.rs | 2 +- src/set.rs | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/map.rs b/src/map.rs index 3a43a79b..d7465fa5 100644 --- a/src/map.rs +++ b/src/map.rs @@ -166,10 +166,7 @@ impl IndexMap { #[inline] pub fn with_capacity_and_hasher(n: usize, hash_builder: S) -> Self { if n == 0 { - IndexMap { - core: IndexMapCore::new(), - hash_builder, - } + Self::with_hasher(hash_builder) } else { IndexMap { core: IndexMapCore::with_capacity(n), @@ -179,8 +176,11 @@ impl IndexMap { } /// Create a new map with `hash_builder` - pub fn with_hasher(hash_builder: S) -> Self { - Self::with_capacity_and_hasher(0, hash_builder) + pub const fn with_hasher(hash_builder: S) -> Self { + IndexMap { + core: IndexMapCore::new(), + hash_builder, + } } /// Computes in **O(1)** time. diff --git a/src/map/core.rs b/src/map/core.rs index c4e725c9..c5ad4f04 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -120,7 +120,7 @@ impl Entries for IndexMapCore { impl IndexMapCore { #[inline] - pub(crate) fn new() -> Self { + pub(crate) const fn new() -> Self { IndexMapCore { indices: RawTable::new(), entries: Vec::new(), diff --git a/src/set.rs b/src/set.rs index 134c9045..4dabac0a 100644 --- a/src/set.rs +++ b/src/set.rs @@ -156,7 +156,7 @@ impl IndexSet { } /// Create a new set with `hash_builder` - pub fn with_hasher(hash_builder: S) -> Self { + pub const fn with_hasher(hash_builder: S) -> Self { IndexSet { map: IndexMap::with_hasher(hash_builder), } From a1a76e19bbd5d9a3807c4445d2327a88553f71f1 Mon Sep 17 00:00:00 2001 From: Max Willsey Date: Thu, 16 Sep 2021 15:10:05 -0700 Subject: [PATCH 026/236] Add note about const-ness of with_hasher --- src/map.rs | 7 ++++++- src/set.rs | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/map.rs b/src/map.rs index d7465fa5..c62b36fc 100644 --- a/src/map.rs +++ b/src/map.rs @@ -175,7 +175,12 @@ impl IndexMap { } } - /// Create a new map with `hash_builder` + /// Create a new map with `hash_builder`. + /// + /// This function is `const`, so it + /// can be called in `static` contexts. + /// If you need `hash_builder` that can be made in the static context, + /// consider [`ahash`](https://docs.rs/ahash/0.7.4/ahash/struct.RandomState.html#method.with_seeds). pub const fn with_hasher(hash_builder: S) -> Self { IndexMap { core: IndexMapCore::new(), diff --git a/src/set.rs b/src/set.rs index 4dabac0a..f6dcf044 100644 --- a/src/set.rs +++ b/src/set.rs @@ -155,7 +155,12 @@ impl IndexSet { } } - /// Create a new set with `hash_builder` + /// Create a new set with `hash_builder`. + /// + /// This function is `const`, so it + /// can be called in `static` contexts. + /// If you need `hash_builder` that can be made in the static context, + /// consider [`ahash`](https://docs.rs/ahash/0.7.4/ahash/struct.RandomState.html#method.with_seeds). pub const fn with_hasher(hash_builder: S) -> Self { IndexSet { map: IndexMap::with_hasher(hash_builder), From 7e56983f705ad82739c3f1bfc3376fcd294496d8 Mon Sep 17 00:00:00 2001 From: Max Willsey Date: Tue, 21 Sep 2021 10:44:02 -0700 Subject: [PATCH 027/236] Remove mention of ahash in docs --- src/map.rs | 2 -- src/set.rs | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/map.rs b/src/map.rs index c62b36fc..04ac372f 100644 --- a/src/map.rs +++ b/src/map.rs @@ -179,8 +179,6 @@ impl IndexMap { /// /// This function is `const`, so it /// can be called in `static` contexts. - /// If you need `hash_builder` that can be made in the static context, - /// consider [`ahash`](https://docs.rs/ahash/0.7.4/ahash/struct.RandomState.html#method.with_seeds). pub const fn with_hasher(hash_builder: S) -> Self { IndexMap { core: IndexMapCore::new(), diff --git a/src/set.rs b/src/set.rs index f6dcf044..e8216a20 100644 --- a/src/set.rs +++ b/src/set.rs @@ -159,8 +159,6 @@ impl IndexSet { /// /// This function is `const`, so it /// can be called in `static` contexts. - /// If you need `hash_builder` that can be made in the static context, - /// consider [`ahash`](https://docs.rs/ahash/0.7.4/ahash/struct.RandomState.html#method.with_seeds). pub const fn with_hasher(hash_builder: S) -> Self { IndexSet { map: IndexMap::with_hasher(hash_builder), From 366ab786b010292ca396b235bda8754cb25e86a6 Mon Sep 17 00:00:00 2001 From: Jakob Degen Date: Sun, 21 Nov 2021 02:01:25 -0500 Subject: [PATCH 028/236] Clarify docs for .pop() --- src/map.rs | 2 ++ src/set.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/map.rs b/src/map.rs index 3a43a79b..acce9bab 100644 --- a/src/map.rs +++ b/src/map.rs @@ -636,6 +636,8 @@ where /// Remove the last key-value pair /// + /// This preserves the order of the remaining elements. + /// /// Computes in **O(1)** time (average). pub fn pop(&mut self) -> Option<(K, V)> { self.core.pop() diff --git a/src/set.rs b/src/set.rs index 134c9045..51e9ef9e 100644 --- a/src/set.rs +++ b/src/set.rs @@ -532,6 +532,8 @@ where /// Remove the last value /// + /// This preserves the order of the remaining elements. + /// /// Computes in **O(1)** time (average). pub fn pop(&mut self) -> Option { self.map.pop().map(|(x, ())| x) From 2a2b16364ac5876de96bbffe4b9d7d8baa2c4004 Mon Sep 17 00:00:00 2001 From: "Brandon H. Gomes" Date: Sat, 4 Dec 2021 16:56:42 -0500 Subject: [PATCH 029/236] feat: add unstable sorting methods --- src/map.rs | 56 +++++++++++++++++++++++++++++++++++++++++++----- src/rayon/map.rs | 39 +++++++++++++++++++++++++++++++-- src/set.rs | 46 +++++++++++++++++++++++++++++++++------ 3 files changed, 128 insertions(+), 13 deletions(-) diff --git a/src/map.rs b/src/map.rs index aa8d06a0..6032c339 100644 --- a/src/map.rs +++ b/src/map.rs @@ -664,18 +664,18 @@ where /// Sort the map’s key-value pairs by the default ordering of the keys. /// - /// See `sort_by` for details. + /// See [`sort_by`](Self::sort_by) for details. pub fn sort_keys(&mut self) where K: Ord, { - self.with_entries(|entries| { - entries.sort_by(|a, b| Ord::cmp(&a.key, &b.key)); + self.with_entries(move |entries| { + entries.sort_by(move |a, b| K::cmp(&a.key, &b.key)); }); } /// Sort the map’s key-value pairs in place using the comparison - /// function `compare`. + /// function `cmp`. /// /// The comparison function receives two key and value pairs to compare (you /// can sort by keys or values or their combination as needed). @@ -691,7 +691,7 @@ where }); } - /// Sort the key-value pairs of the map and return a by value iterator of + /// Sort the key-value pairs of the map and return a by-value iterator of /// the key-value pairs with the result. /// /// The sort is stable. @@ -706,6 +706,52 @@ where } } + /// Sort the map's key-value pairs by the default ordering of the keys, but + /// may not preserve the order of equal elements. + /// + /// See [`sort_unstable_by`](Self::sort_unstable_by) for details. + pub fn sort_unstable_keys(&mut self) + where + K: Ord, + { + self.with_entries(move |entries| { + entries.sort_unstable_by(move |a, b| K::cmp(&a.key, &b.key)); + }); + } + + /// Sort the map's key-value pairs in place using the comparison function `cmp`, but + /// may not preserve the order of equal elements. + /// + /// The comparison function receives two key and value pairs to compare (you + /// can sort by keys or values or their combination as needed). + /// + /// Computes in **O(n log n + c)** time and **O(n)** space where *n* is + /// the length of the map and *c* is the capacity. The sort is unstable. + pub fn sort_unstable_by(&mut self, mut cmp: F) + where + F: FnMut(&K, &V, &K, &V) -> Ordering, + { + self.with_entries(move |entries| { + entries.sort_unstable_by(move |a, b| cmp(&a.key, &a.value, &b.key, &b.value)); + }); + } + + /// Sort the key-value pairs of the map and return a by-value iterator of + /// the key-value pairs with the result. + /// + /// The sort is unstable. + #[inline] + pub fn sorted_unstable_by(self, mut cmp: F) -> IntoIter + where + F: FnMut(&K, &V, &K, &V) -> Ordering, + { + let mut entries = self.into_entries(); + entries.sort_unstable_by(move |a, b| cmp(&a.key, &a.value, &b.key, &b.value)); + IntoIter { + iter: entries.into_iter(), + } + } + /// Reverses the order of the map’s key-value pairs in place. /// /// Computes in **O(n)** time and **O(1)** space. diff --git a/src/rayon/map.rs b/src/rayon/map.rs index ed2da3ef..abea8a13 100644 --- a/src/rayon/map.rs +++ b/src/rayon/map.rs @@ -303,7 +303,7 @@ where } /// Sort the map’s key-value pairs in place and in parallel, using the comparison - /// function `compare`. + /// function `cmp`. /// /// The comparison function receives two key and value pairs to compare (you /// can sort by keys or values or their combination as needed). @@ -316,7 +316,7 @@ where }); } - /// Sort the key-value pairs of the map in parallel and return a by value parallel + /// Sort the key-value pairs of the map in parallel and return a by-value parallel /// iterator of the key-value pairs with the result. pub fn par_sorted_by(self, cmp: F) -> IntoParIter where @@ -326,6 +326,41 @@ where entries.par_sort_by(move |a, b| cmp(&a.key, &a.value, &b.key, &b.value)); IntoParIter { entries } } + + /// Sort the map's key-value pairs in parallel, by the default ordering of the keys. + pub fn par_sort_unstable_keys(&mut self) + where + K: Ord, + { + self.with_entries(|entries| { + entries.par_sort_unstable_by(|a, b| K::cmp(&a.key, &b.key)); + }); + } + + /// Sort the map's key-value pairs in place and in parallel, using the comparison + /// function `cmp`. + /// + /// The comparison function receives two key and value pairs to compare (you + /// can sort by keys or values or their combination as needed). + pub fn par_sort_unstable_by(&mut self, cmp: F) + where + F: Fn(&K, &V, &K, &V) -> Ordering + Sync, + { + self.with_entries(|entries| { + entries.par_sort_unstable_by(move |a, b| cmp(&a.key, &a.value, &b.key, &b.value)); + }); + } + + /// Sort the key-value pairs of the map in parallel and return a by-value parallel + /// iterator of the key-value pairs with the result. + pub fn par_sorted_unstable_by(self, cmp: F) -> IntoParIter + where + F: Fn(&K, &V, &K, &V) -> Ordering + Sync, + { + let mut entries = self.into_entries(); + entries.par_sort_unstable_by(move |a, b| cmp(&a.key, &a.value, &b.key, &b.value)); + IntoParIter { entries } + } } /// A parallel mutable iterator over the values of a `IndexMap`. diff --git a/src/set.rs b/src/set.rs index 66c3da4d..72a386c5 100644 --- a/src/set.rs +++ b/src/set.rs @@ -553,7 +553,7 @@ where /// Sort the set’s values by their default ordering. /// - /// See `sort_by` for details. + /// See [`sort_by`](Self::sort_by) for details. pub fn sort(&mut self) where T: Ord, @@ -561,17 +561,17 @@ where self.map.sort_keys() } - /// Sort the set’s values in place using the comparison function `compare`. + /// Sort the set’s values in place using the comparison function `cmp`. /// /// Computes in **O(n log n)** time and **O(n)** space. The sort is stable. - pub fn sort_by(&mut self, mut compare: F) + pub fn sort_by(&mut self, mut cmp: F) where F: FnMut(&T, &T) -> Ordering, { - self.map.sort_by(move |a, _, b, _| compare(a, b)); + self.map.sort_by(move |a, _, b, _| cmp(a, b)); } - /// Sort the values of the set and return a by value iterator of + /// Sort the values of the set and return a by-value iterator of /// the values with the result. /// /// The sort is stable. @@ -580,7 +580,41 @@ where F: FnMut(&T, &T) -> Ordering, { IntoIter { - iter: self.map.sorted_by(move |a, &(), b, &()| cmp(a, b)).iter, + iter: self.map.sorted_by(move |a, _, b, _| cmp(a, b)).iter, + } + } + + /// Sort the set's values by their default ordering. + /// + /// See [`sort_unstable_by`](Self::sort_unstable_by) for details. + pub fn sort_unstable(&mut self) + where + T: Ord, + { + self.map.sort_unstable_keys() + } + + /// Sort the set's values in place using the comparison funtion `cmp`. + /// + /// Computes in **O(n log n)** time and **O(n)** space. The sort is unstable. + pub fn sort_unstable_by(&mut self, mut cmp: F) + where + F: FnMut(&T, &T) -> Ordering, + { + self.map.sort_unstable_by(move |a, _, b, _| cmp(a, b)) + } + + /// Sort the values of the set and return a by-value iterator of + /// the values with the result. + pub fn sorted_unstable_by(self, mut cmp: F) -> IntoIter + where + F: FnMut(&T, &T) -> Ordering, + { + IntoIter { + iter: self + .map + .sorted_unstable_by(move |a, _, b, _| cmp(a, b)) + .iter, } } From 1de6f77c8818bf2534bebd2a9e2a0838923e9929 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 9 Dec 2021 11:36:50 -0800 Subject: [PATCH 030/236] Add minimal support for rustc-rayon --- .github/workflows/ci.yml | 2 + Cargo.toml | 4 + src/lib.rs | 3 + src/macros.rs | 54 +++++++++++++ src/rayon/mod.rs | 52 ------------- src/rustc.rs | 158 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 221 insertions(+), 52 deletions(-) create mode 100644 src/rustc.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a399c1f8..b4505705 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,8 @@ jobs: features: serde - rust: stable features: rayon + - rust: stable + features: rustc-rayon - rust: stable features: std - rust: beta diff --git a/Cargo.toml b/Cargo.toml index d099405d..c18dfce2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,10 @@ autocfg = "1" serde = { version = "1.0", optional = true, default-features = false } rayon = { version = "1.2", optional = true } +# Internal feature, only used when building as part of rustc, +# not part of the stable interface of this crate. +rustc-rayon = { version = "0.3", optional = true } + [dependencies.hashbrown] version = "0.11" default-features = false diff --git a/src/lib.rs b/src/lib.rs index 489e5486..8c44d7bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -103,6 +103,9 @@ pub mod set; #[cfg(feature = "rayon")] mod rayon; +#[cfg(feature = "rustc-rayon")] +mod rustc; + pub use crate::equivalent::Equivalent; pub use crate::map::IndexMap; pub use crate::set::IndexSet; diff --git a/src/macros.rs b/src/macros.rs index a7b33508..ca26287b 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -122,3 +122,57 @@ macro_rules! double_ended_iterator_methods { } }; } + +// generate `ParallelIterator` methods by just forwarding to the underlying +// self.entries and mapping its elements. +#[cfg(any(feature = "rayon", feature = "rustc-rayon"))] +macro_rules! parallel_iterator_methods { + // $map_elt is the mapping function from the underlying iterator's element + ($map_elt:expr) => { + fn drive_unindexed(self, consumer: C) -> C::Result + where + C: UnindexedConsumer, + { + self.entries + .into_par_iter() + .map($map_elt) + .drive_unindexed(consumer) + } + + // NB: This allows indexed collection, e.g. directly into a `Vec`, but the + // underlying iterator must really be indexed. We should remove this if we + // start having tombstones that must be filtered out. + fn opt_len(&self) -> Option { + Some(self.entries.len()) + } + }; +} + +// generate `IndexedParallelIterator` methods by just forwarding to the underlying +// self.entries and mapping its elements. +#[cfg(any(feature = "rayon", feature = "rustc-rayon"))] +macro_rules! indexed_parallel_iterator_methods { + // $map_elt is the mapping function from the underlying iterator's element + ($map_elt:expr) => { + fn drive(self, consumer: C) -> C::Result + where + C: Consumer, + { + self.entries.into_par_iter().map($map_elt).drive(consumer) + } + + fn len(&self) -> usize { + self.entries.len() + } + + fn with_producer(self, callback: CB) -> CB::Output + where + CB: ProducerCallback, + { + self.entries + .into_par_iter() + .map($map_elt) + .with_producer(callback) + } + }; +} diff --git a/src/rayon/mod.rs b/src/rayon/mod.rs index 757a54dd..ebb1ac2d 100644 --- a/src/rayon/mod.rs +++ b/src/rayon/mod.rs @@ -4,58 +4,6 @@ use alloc::collections::LinkedList; use crate::vec::Vec; -// generate `ParallelIterator` methods by just forwarding to the underlying -// self.entries and mapping its elements. -macro_rules! parallel_iterator_methods { - // $map_elt is the mapping function from the underlying iterator's element - ($map_elt:expr) => { - fn drive_unindexed(self, consumer: C) -> C::Result - where - C: UnindexedConsumer, - { - self.entries - .into_par_iter() - .map($map_elt) - .drive_unindexed(consumer) - } - - // NB: This allows indexed collection, e.g. directly into a `Vec`, but the - // underlying iterator must really be indexed. We should remove this if we - // start having tombstones that must be filtered out. - fn opt_len(&self) -> Option { - Some(self.entries.len()) - } - }; -} - -// generate `IndexedParallelIterator` methods by just forwarding to the underlying -// self.entries and mapping its elements. -macro_rules! indexed_parallel_iterator_methods { - // $map_elt is the mapping function from the underlying iterator's element - ($map_elt:expr) => { - fn drive(self, consumer: C) -> C::Result - where - C: Consumer, - { - self.entries.into_par_iter().map($map_elt).drive(consumer) - } - - fn len(&self) -> usize { - self.entries.len() - } - - fn with_producer(self, callback: CB) -> CB::Output - where - CB: ProducerCallback, - { - self.entries - .into_par_iter() - .map($map_elt) - .with_producer(callback) - } - }; -} - pub mod map; pub mod set; diff --git a/src/rustc.rs b/src/rustc.rs new file mode 100644 index 00000000..b843858b --- /dev/null +++ b/src/rustc.rs @@ -0,0 +1,158 @@ +//! Minimal support for `rustc-rayon`, not intended for general use. + +use crate::vec::Vec; +use crate::{Bucket, Entries, IndexMap, IndexSet}; + +use rustc_rayon::iter::plumbing::{Consumer, ProducerCallback, UnindexedConsumer}; +use rustc_rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; + +mod map { + use super::*; + + impl IntoParallelIterator for IndexMap + where + K: Send, + V: Send, + { + type Item = (K, V); + type Iter = IntoParIter; + + fn into_par_iter(self) -> Self::Iter { + IntoParIter { + entries: self.into_entries(), + } + } + } + + pub struct IntoParIter { + entries: Vec>, + } + + impl ParallelIterator for IntoParIter { + type Item = (K, V); + + parallel_iterator_methods!(Bucket::key_value); + } + + impl IndexedParallelIterator for IntoParIter { + indexed_parallel_iterator_methods!(Bucket::key_value); + } + + impl<'a, K, V, S> IntoParallelIterator for &'a IndexMap + where + K: Sync, + V: Sync, + { + type Item = (&'a K, &'a V); + type Iter = ParIter<'a, K, V>; + + fn into_par_iter(self) -> Self::Iter { + ParIter { + entries: self.as_entries(), + } + } + } + + pub struct ParIter<'a, K, V> { + entries: &'a [Bucket], + } + + impl<'a, K: Sync, V: Sync> ParallelIterator for ParIter<'a, K, V> { + type Item = (&'a K, &'a V); + + parallel_iterator_methods!(Bucket::refs); + } + + impl IndexedParallelIterator for ParIter<'_, K, V> { + indexed_parallel_iterator_methods!(Bucket::refs); + } + + impl<'a, K, V, S> IntoParallelIterator for &'a mut IndexMap + where + K: Sync + Send, + V: Send, + { + type Item = (&'a K, &'a mut V); + type Iter = ParIterMut<'a, K, V>; + + fn into_par_iter(self) -> Self::Iter { + ParIterMut { + entries: self.as_entries_mut(), + } + } + } + + pub struct ParIterMut<'a, K, V> { + entries: &'a mut [Bucket], + } + + impl<'a, K: Sync + Send, V: Send> ParallelIterator for ParIterMut<'a, K, V> { + type Item = (&'a K, &'a mut V); + + parallel_iterator_methods!(Bucket::ref_mut); + } + + impl IndexedParallelIterator for ParIterMut<'_, K, V> { + indexed_parallel_iterator_methods!(Bucket::ref_mut); + } +} + +mod set { + use super::*; + + impl IntoParallelIterator for IndexSet + where + T: Send, + { + type Item = T; + type Iter = IntoParIter; + + fn into_par_iter(self) -> Self::Iter { + IntoParIter { + entries: self.into_entries(), + } + } + } + + pub struct IntoParIter { + entries: Vec>, + } + + impl ParallelIterator for IntoParIter { + type Item = T; + + parallel_iterator_methods!(Bucket::key); + } + + impl IndexedParallelIterator for IntoParIter { + indexed_parallel_iterator_methods!(Bucket::key); + } + + impl<'a, T, S> IntoParallelIterator for &'a IndexSet + where + T: Sync, + { + type Item = &'a T; + type Iter = ParIter<'a, T>; + + fn into_par_iter(self) -> Self::Iter { + ParIter { + entries: self.as_entries(), + } + } + } + + pub struct ParIter<'a, T> { + entries: &'a [Bucket], + } + + impl<'a, T: Sync> ParallelIterator for ParIter<'a, T> { + type Item = &'a T; + + parallel_iterator_methods!(Bucket::key_ref); + } + + impl IndexedParallelIterator for ParIter<'_, T> { + indexed_parallel_iterator_methods!(Bucket::key_ref); + } +} From 7bdcfc0f70c30c6d8a91409fc00dbec8784541d5 Mon Sep 17 00:00:00 2001 From: "Brandon H. Gomes" Date: Thu, 9 Dec 2021 17:03:09 -0500 Subject: [PATCH 031/236] fix: add forgotten set parallel unstable sorting methods --- src/rayon/set.rs | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/rayon/set.rs b/src/rayon/set.rs index f3e0da91..a6e1251f 100644 --- a/src/rayon/set.rs +++ b/src/rayon/set.rs @@ -452,7 +452,7 @@ where }); } - /// Sort the set’s values in place and in parallel, using the comparison function `compare`. + /// Sort the set’s values in place and in parallel, using the comparison function `cmp`. pub fn par_sort_by(&mut self, cmp: F) where F: Fn(&T, &T) -> Ordering + Sync, @@ -462,7 +462,7 @@ where }); } - /// Sort the values of the set in parallel and return a by value parallel iterator of + /// Sort the values of the set in parallel and return a by-value parallel iterator of /// the values with the result. pub fn par_sorted_by(self, cmp: F) -> IntoParIter where @@ -472,6 +472,37 @@ where entries.par_sort_by(move |a, b| cmp(&a.key, &b.key)); IntoParIter { entries } } + + /// Sort the set's values in parallel by their default ordering. + pub fn par_sort_unstable(&mut self) + where + T: Ord, + { + self.with_entries(|entries| { + entries.par_sort_unstable_by(|a, b| T::cmp(&a.key, &b.key)); + }); + } + + /// Sort the set’s values in place and in parallel, using the comparison function `cmp`. + pub fn par_sort_unstable_by(&mut self, cmp: F) + where + F: Fn(&T, &T) -> Ordering + Sync, + { + self.with_entries(|entries| { + entries.par_sort_unstable_by(move |a, b| cmp(&a.key, &b.key)); + }); + } + + /// Sort the values of the set in parallel and return a by-value parallel iterator of + /// the values with the result. + pub fn par_sorted_unstable_by(self, cmp: F) -> IntoParIter + where + F: Fn(&T, &T) -> Ordering + Sync, + { + let mut entries = self.into_entries(); + entries.par_sort_unstable_by(move |a, b| cmp(&a.key, &b.key)); + IntoParIter { entries } + } } /// Requires crate feature `"rayon"`. From f0159f656d95d19b681e63b827538f6d0ca3367b Mon Sep 17 00:00:00 2001 From: Andy Freeland Date: Thu, 21 Oct 2021 14:06:55 -0700 Subject: [PATCH 032/236] Add `IndexMap::from(array)` and `IndexSet::from(array)` This patch adds `IndexMap::from(array)` and `IndexSet::from(array)` to match the API for `HashMap`, `HashSet`, etc. as of https://github.com/rust-lang/rust/pull/84111. --- src/map.rs | 29 +++++++++++++++++++++++++++++ src/set.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/src/map.rs b/src/map.rs index 206e7eef..ad76a473 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1345,6 +1345,25 @@ where } } +#[cfg(has_std)] +impl From<[(K, V); N]> for IndexMap +where + K: Hash + Eq, +{ + /// # Examples + /// + /// ``` + /// use indexmap::IndexMap; + /// + /// let map1 = IndexMap::from([(1, 2), (3, 4)]); + /// let map2: IndexMap<_, _> = [(1, 2), (3, 4)].into(); + /// assert_eq!(map1, map2); + /// ``` + fn from(arr: [(K, V); N]) -> Self { + std::array::IntoIter::new(arr).collect() + } +} + impl Extend<(K, V)> for IndexMap where K: Hash + Eq, @@ -1839,4 +1858,14 @@ mod tests { assert!(values.contains(&'b')); assert!(values.contains(&'c')); } + + #[test] + fn from_array() { + let map = IndexMap::from([(1, 2), (3, 4)]); + let mut expected = IndexMap::new(); + expected.insert(1, 2); + expected.insert(3, 4); + + assert_eq!(map, expected) + } } diff --git a/src/set.rs b/src/set.rs index 5aabcf51..0322d25d 100644 --- a/src/set.rs +++ b/src/set.rs @@ -843,6 +843,25 @@ where } } +#[cfg(has_std)] +impl From<[T; N]> for IndexSet +where + T: Eq + Hash, +{ + /// # Examples + /// + /// ``` + /// use indexmap::IndexSet; + /// + /// let set1 = IndexSet::from([1, 2, 3, 4]); + /// let set2: IndexSet<_> = [1, 2, 3, 4].into(); + /// assert_eq!(set1, set2); + /// ``` + fn from(arr: [T; N]) -> Self { + std::array::IntoIter::new(arr).collect() + } +} + impl Extend for IndexSet where T: Hash + Eq, @@ -1710,4 +1729,12 @@ mod tests { assert_eq!(&set_c - &set_d, set_a); assert_eq!(&set_d - &set_c, &set_d - &set_b); } + + #[test] + fn from_array() { + let set1 = IndexSet::from([1, 2, 3, 4]); + let set2: IndexSet<_> = [1, 2, 3, 4].into(); + + assert_eq!(set1, set2); + } } From 5d2ce528b3c431722581526b175a51528ae0efa0 Mon Sep 17 00:00:00 2001 From: Andy Freeland Date: Sun, 24 Oct 2021 13:15:04 -0700 Subject: [PATCH 033/236] Require rustc 1.51+ for `IndexMap::from(array)` and `IndexSet::from(array)` https://github.com/rust-lang/rust/issues/74878 and https://github.com/rust-lang/rust/issues/65798 were both stabilized in 1.51. --- build.rs | 1 + src/map.rs | 3 ++- src/set.rs | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/build.rs b/build.rs index 9f9fa054..7c5b6d5e 100644 --- a/build.rs +++ b/build.rs @@ -4,5 +4,6 @@ fn main() { Some(_) => autocfg::emit("has_std"), None => autocfg::new().emit_sysroot_crate("std"), } + autocfg::new().emit_rustc_version(1, 51); autocfg::rerun_path("build.rs"); } diff --git a/src/map.rs b/src/map.rs index ad76a473..ca51b545 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1345,7 +1345,7 @@ where } } -#[cfg(has_std)] +#[cfg(all(has_std, rustc_1_51))] impl From<[(K, V); N]> for IndexMap where K: Hash + Eq, @@ -1860,6 +1860,7 @@ mod tests { } #[test] + #[cfg(all(has_std, rustc_1_51))] fn from_array() { let map = IndexMap::from([(1, 2), (3, 4)]); let mut expected = IndexMap::new(); diff --git a/src/set.rs b/src/set.rs index 0322d25d..f7d4b431 100644 --- a/src/set.rs +++ b/src/set.rs @@ -843,7 +843,7 @@ where } } -#[cfg(has_std)] +#[cfg(all(has_std, rustc_1_51))] impl From<[T; N]> for IndexSet where T: Eq + Hash, @@ -1731,6 +1731,7 @@ mod tests { } #[test] + #[cfg(all(has_std, rustc_1_51))] fn from_array() { let set1 = IndexSet::from([1, 2, 3, 4]); let set2: IndexSet<_> = [1, 2, 3, 4].into(); From 6fca269adf18b1dd0ef0e62f5e8744c7cba51725 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 6 Jan 2022 10:39:49 -0800 Subject: [PATCH 034/236] No extra space is used in unstable sorts --- src/map.rs | 2 +- src/set.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/map.rs b/src/map.rs index 6032c339..76993c36 100644 --- a/src/map.rs +++ b/src/map.rs @@ -725,7 +725,7 @@ where /// The comparison function receives two key and value pairs to compare (you /// can sort by keys or values or their combination as needed). /// - /// Computes in **O(n log n + c)** time and **O(n)** space where *n* is + /// Computes in **O(n log n + c)** time where *n* is /// the length of the map and *c* is the capacity. The sort is unstable. pub fn sort_unstable_by(&mut self, mut cmp: F) where diff --git a/src/set.rs b/src/set.rs index 72a386c5..ddd1cc38 100644 --- a/src/set.rs +++ b/src/set.rs @@ -596,7 +596,7 @@ where /// Sort the set's values in place using the comparison funtion `cmp`. /// - /// Computes in **O(n log n)** time and **O(n)** space. The sort is unstable. + /// Computes in **O(n log n)** time. The sort is unstable. pub fn sort_unstable_by(&mut self, mut cmp: F) where F: FnMut(&T, &T) -> Ordering, From 5a14f7bb8af6e3c8c4fe52bdd2978da07126cbbe Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 6 Jan 2022 11:10:12 -0800 Subject: [PATCH 035/236] Move recent changes to RELEASES.rst --- README.rst | 318 +-------------------------------------------------- RELEASES.rst | 317 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 318 insertions(+), 317 deletions(-) create mode 100644 RELEASES.rst diff --git a/README.rst b/README.rst index e4617f8e..da76a68b 100644 --- a/README.rst +++ b/README.rst @@ -66,320 +66,4 @@ which is roughly: Recent Changes ============== -- 1.7.0 - - - **MSRV**: Rust 1.49 or later is now required. - - - The ``hashbrown`` dependency has been updated to version 0.11. - -- 1.6.2 - - - Fixed to match ``std`` behavior, ``OccupiedEntry::key`` now references the - existing key in the map instead of the lookup key, by @cuviper in PR 170_. - - - The new ``Entry::or_insert_with_key`` matches Rust 1.50's ``Entry`` method, - passing ``&K`` to the callback to create a value, by @cuviper in PR 175_. - -.. _170: https://github.com/bluss/indexmap/pull/170 -.. _175: https://github.com/bluss/indexmap/pull/175 - -- 1.6.1 - - - The new ``serde_seq`` module implements ``IndexMap`` serialization as a - sequence to ensure order is preserved, by @cuviper in PR 158_. - - - New methods on maps and sets work like the ``Vec``/slice methods by the same name: - ``truncate``, ``split_off``, ``first``, ``first_mut``, ``last``, ``last_mut``, and - ``swap_indices``, by @cuviper in PR 160_. - -.. _158: https://github.com/bluss/indexmap/pull/158 -.. _160: https://github.com/bluss/indexmap/pull/160 - -- 1.6.0 - - - **MSRV**: Rust 1.36 or later is now required. - - - The ``hashbrown`` dependency has been updated to version 0.9. - -- 1.5.2 - - - The new "std" feature will force the use of ``std`` for users that explicitly - want the default ``S = RandomState``, bypassing the autodetection added in 1.3.0, - by @cuviper in PR 145_. - -.. _145: https://github.com/bluss/indexmap/pull/145 - -- 1.5.1 - - - Values can now be indexed by their ``usize`` position by @cuviper in PR 132_. - - - Some of the generic bounds have been relaxed to match ``std`` by @cuviper in PR 141_. - - - ``drain`` now accepts any ``R: RangeBounds`` by @cuviper in PR 142_. - -.. _132: https://github.com/bluss/indexmap/pull/132 -.. _141: https://github.com/bluss/indexmap/pull/141 -.. _142: https://github.com/bluss/indexmap/pull/142 - -- 1.5.0 - - - **MSRV**: Rust 1.32 or later is now required. - - - The inner hash table is now based on ``hashbrown`` by @cuviper in PR 131_. - This also completes the method ``reserve`` and adds ``shrink_to_fit``. - - - Add new methods ``get_key_value``, ``remove_entry``, ``swap_remove_entry``, - and ``shift_remove_entry``, by @cuviper in PR 136_ - - - ``Clone::clone_from`` reuses allocations by @cuviper in PR 125_ - - - Add new method ``reverse`` by @linclelinkpart5 in PR 128_ - -.. _125: https://github.com/bluss/indexmap/pull/125 -.. _128: https://github.com/bluss/indexmap/pull/128 -.. _131: https://github.com/bluss/indexmap/pull/131 -.. _136: https://github.com/bluss/indexmap/pull/136 - -- 1.4.0 - - - Add new method ``get_index_of`` by @Thermatrix in PR 115_ and 120_ - - - Fix build script rebuild-if-changed configuration to use "build.rs"; - fixes issue 123_. Fix by @cuviper. - - - Dev-dependencies (rand and quickcheck) have been updated. The crate's tests - now run using Rust 1.32 or later (MSRV for building the crate has not changed). - by @kjeremy and @bluss - -.. _123: https://github.com/bluss/indexmap/issues/123 -.. _115: https://github.com/bluss/indexmap/pull/115 -.. _120: https://github.com/bluss/indexmap/pull/120 - -- 1.3.2 - - - Maintenance update to regenerate the published `Cargo.toml`. - -- 1.3.1 - - - Maintenance update for formatting and ``autocfg`` 1.0. - -- 1.3.0 - - - The deprecation messages in the previous version have been removed. - (The methods have not otherwise changed.) Docs for removal methods have been - improved. - - From Rust 1.36, this crate supports being built **without std**, requiring - ``alloc`` instead. This is enabled automatically when it is detected that - ``std`` is not available. There is no crate feature to enable/disable to - trigger this. The new build-dep ``autocfg`` enables this. - -- 1.2.0 - - - Plain ``.remove()`` now has a deprecation message, it informs the user - about picking one of the removal functions ``swap_remove`` and ``shift_remove`` - which have different performance and order semantics. - Plain ``.remove()`` will not be removed, the warning message and method - will remain until further. - - - Add new method ``shift_remove`` for order preserving removal on the map, - and ``shift_take`` for the corresponding operation on the set. - - - Add methods ``swap_remove``, ``swap_remove_entry`` to ``Entry``. - - - Fix indexset/indexmap to support full paths, like ``indexmap::indexmap!()`` - - - Internal improvements: fix warnings, deprecations and style lints - -- 1.1.0 - - - Added optional feature `"rayon"` that adds parallel iterator support - to `IndexMap` and `IndexSet` using Rayon. This includes all the regular - iterators in parallel versions, and parallel sort. - - - Implemented ``Clone`` for ``map::{Iter, Keys, Values}`` and - ``set::{Difference, Intersection, Iter, SymmetricDifference, Union}`` - - - Implemented ``Debug`` for ``map::{Entry, IntoIter, Iter, Keys, Values}`` and - ``set::{Difference, Intersection, IntoIter, Iter, SymmetricDifference, Union}`` - - - Serde trait ``IntoDeserializer`` are implemented for ``IndexMap`` and ``IndexSet``. - - - Minimum Rust version requirement increased to Rust 1.30 for development builds. - -- 1.0.2 - - - The new methods ``IndexMap::insert_full`` and ``IndexSet::insert_full`` are - both like ``insert`` with the index included in the return value. - - - The new method ``Entry::and_modify`` can be used to modify occupied - entries, matching the new methods of ``std`` maps in Rust 1.26. - - - The new method ``Entry::or_default`` inserts a default value in unoccupied - entries, matching the new methods of ``std`` maps in Rust 1.28. - -- 1.0.1 - - - Document Rust version policy for the crate (see rustdoc) - -- 1.0.0 - - - This is the 1.0 release for ``indexmap``! (the crate and datastructure - formerly known as “ordermap”) - - ``OccupiedEntry::insert`` changed its signature, to use ``&mut self`` for - the method receiver, matching the equivalent method for a standard - ``HashMap``. Thanks to @dtolnay for finding this bug. - - The deprecated old names from ordermap were removed: ``OrderMap``, - ``OrderSet``, ``ordermap!{}``, ``orderset!{}``. Use the new ``IndexMap`` - etc names instead. - -- 0.4.1 - - - Renamed crate to ``indexmap``; the ``ordermap`` crate is now deprecated - and the types ``OrderMap/Set`` now have a deprecation notice. - -- 0.4.0 - - - This is the last release series for this ``ordermap`` under that name, - because the crate is **going to be renamed** to ``indexmap`` (with types - ``IndexMap``, ``IndexSet``) and no change in functionality! - - The map and its associated structs moved into the ``map`` submodule of the - crate, so that the map and set are symmetric - - + The iterators, ``Entry`` and other structs are now under ``ordermap::map::`` - - - Internally refactored ``OrderMap`` so that all the main algorithms - (insertion, lookup, removal etc) that don't use the ``S`` parameter (the - hasher) are compiled without depending on ``S``, which reduces generics bloat. - - - ``Entry`` no longer has a type parameter ``S``, which is just like - the standard ``HashMap``'s entry. - - - Minimum Rust version requirement increased to Rust 1.18 - -- 0.3.5 - - - Documentation improvements - -- 0.3.4 - - - The ``.retain()`` methods for ``OrderMap`` and ``OrderSet`` now - traverse the elements in order, and the retained elements **keep their order** - - Added new methods ``.sort_by()``, ``.sort_keys()`` to ``OrderMap`` and - ``.sort_by()``, ``.sort()`` to ``OrderSet``. These methods allow you to - sort the maps in place efficiently. - -- 0.3.3 - - - Document insertion behaviour better by @lucab - - Updated dependences (no feature changes) by @ignatenkobrain - -- 0.3.2 - - - Add ``OrderSet`` by @cuviper! - - ``OrderMap::drain`` is now (too) a double ended iterator. - -- 0.3.1 - - - In all ordermap iterators, forward the ``collect`` method to the underlying - iterator as well. - - Add crates.io categories. - -- 0.3.0 - - - The methods ``get_pair``, ``get_pair_index`` were both replaced by - ``get_full`` (and the same for the mutable case). - - Method ``swap_remove_pair`` replaced by ``swap_remove_full``. - - Add trait ``MutableKeys`` for opt-in mutable key access. Mutable key access - is only possible through the methods of this extension trait. - - Add new trait ``Equivalent`` for key equivalence. This extends the - ``Borrow`` trait mechanism for ``OrderMap::get`` in a backwards compatible - way, just some minor type inference related issues may become apparent. - See `#10`__ for more information. - - Implement ``Extend<(&K, &V)>`` by @xfix. - -__ https://github.com/bluss/ordermap/pull/10 - -- 0.2.13 - - - Fix deserialization to support custom hashers by @Techcable. - - Add methods ``.index()`` on the entry types by @garro95. - -- 0.2.12 - - - Add methods ``.with_hasher()``, ``.hasher()``. - -- 0.2.11 - - - Support ``ExactSizeIterator`` for the iterators. By @Binero. - - Use ``Box<[Pos]>`` internally, saving a word in the ``OrderMap`` struct. - - Serde support, with crate feature ``"serde-1"``. By @xfix. - -- 0.2.10 - - - Add iterator ``.drain(..)`` by @stevej. - -- 0.2.9 - - - Add method ``.is_empty()`` by @overvenus. - - Implement ``PartialEq, Eq`` by @overvenus. - - Add method ``.sorted_by()``. - -- 0.2.8 - - - Add iterators ``.values()`` and ``.values_mut()``. - - Fix compatibility with 32-bit platforms. - -- 0.2.7 - - - Add ``.retain()``. - -- 0.2.6 - - - Add ``OccupiedEntry::remove_entry`` and other minor entry methods, - so that it now has all the features of ``HashMap``'s entries. - -- 0.2.5 - - - Improved ``.pop()`` slightly. - -- 0.2.4 - - - Improved performance of ``.insert()`` (`#3`__) by @pczarn. - -__ https://github.com/bluss/ordermap/pull/3 - -- 0.2.3 - - - Generalize ``Entry`` for now, so that it works on hashmaps with non-default - hasher. However, there's a lingering compat issue since libstd ``HashMap`` - does not parameterize its entries by the hasher (``S`` typarm). - - Special case some iterator methods like ``.nth()``. - -- 0.2.2 - - - Disable the verbose ``Debug`` impl by default. - -- 0.2.1 - - - Fix doc links and clarify docs. - -- 0.2.0 - - - Add more ``HashMap`` methods & compat with its API. - - Experimental support for ``.entry()`` (the simplest parts of the API). - - Add ``.reserve()`` (placeholder impl). - - Add ``.remove()`` as synonym for ``.swap_remove()``. - - Changed ``.insert()`` to swap value if the entry already exists, and - return ``Option``. - - Experimental support as an *indexed* hash map! Added methods - ``.get_index()``, ``.get_index_mut()``, ``.swap_remove_index()``, - ``.get_pair_index()``, ``.get_pair_index_mut()``. - -- 0.1.2 - - - Implement the 32/32 split idea for ``Pos`` which improves cache utilization - and lookup performance. - -- 0.1.1 - - - Initial release. +See `RELEASES.rst <./RELEASES.rst>`_. diff --git a/RELEASES.rst b/RELEASES.rst new file mode 100644 index 00000000..5bb6394f --- /dev/null +++ b/RELEASES.rst @@ -0,0 +1,317 @@ +- 1.7.0 + + - **MSRV**: Rust 1.49 or later is now required. + + - The ``hashbrown`` dependency has been updated to version 0.11. + +- 1.6.2 + + - Fixed to match ``std`` behavior, ``OccupiedEntry::key`` now references the + existing key in the map instead of the lookup key, by @cuviper in PR 170_. + + - The new ``Entry::or_insert_with_key`` matches Rust 1.50's ``Entry`` method, + passing ``&K`` to the callback to create a value, by @cuviper in PR 175_. + +.. _170: https://github.com/bluss/indexmap/pull/170 +.. _175: https://github.com/bluss/indexmap/pull/175 + +- 1.6.1 + + - The new ``serde_seq`` module implements ``IndexMap`` serialization as a + sequence to ensure order is preserved, by @cuviper in PR 158_. + + - New methods on maps and sets work like the ``Vec``/slice methods by the same name: + ``truncate``, ``split_off``, ``first``, ``first_mut``, ``last``, ``last_mut``, and + ``swap_indices``, by @cuviper in PR 160_. + +.. _158: https://github.com/bluss/indexmap/pull/158 +.. _160: https://github.com/bluss/indexmap/pull/160 + +- 1.6.0 + + - **MSRV**: Rust 1.36 or later is now required. + + - The ``hashbrown`` dependency has been updated to version 0.9. + +- 1.5.2 + + - The new "std" feature will force the use of ``std`` for users that explicitly + want the default ``S = RandomState``, bypassing the autodetection added in 1.3.0, + by @cuviper in PR 145_. + +.. _145: https://github.com/bluss/indexmap/pull/145 + +- 1.5.1 + + - Values can now be indexed by their ``usize`` position by @cuviper in PR 132_. + + - Some of the generic bounds have been relaxed to match ``std`` by @cuviper in PR 141_. + + - ``drain`` now accepts any ``R: RangeBounds`` by @cuviper in PR 142_. + +.. _132: https://github.com/bluss/indexmap/pull/132 +.. _141: https://github.com/bluss/indexmap/pull/141 +.. _142: https://github.com/bluss/indexmap/pull/142 + +- 1.5.0 + + - **MSRV**: Rust 1.32 or later is now required. + + - The inner hash table is now based on ``hashbrown`` by @cuviper in PR 131_. + This also completes the method ``reserve`` and adds ``shrink_to_fit``. + + - Add new methods ``get_key_value``, ``remove_entry``, ``swap_remove_entry``, + and ``shift_remove_entry``, by @cuviper in PR 136_ + + - ``Clone::clone_from`` reuses allocations by @cuviper in PR 125_ + + - Add new method ``reverse`` by @linclelinkpart5 in PR 128_ + +.. _125: https://github.com/bluss/indexmap/pull/125 +.. _128: https://github.com/bluss/indexmap/pull/128 +.. _131: https://github.com/bluss/indexmap/pull/131 +.. _136: https://github.com/bluss/indexmap/pull/136 + +- 1.4.0 + + - Add new method ``get_index_of`` by @Thermatrix in PR 115_ and 120_ + + - Fix build script rebuild-if-changed configuration to use "build.rs"; + fixes issue 123_. Fix by @cuviper. + + - Dev-dependencies (rand and quickcheck) have been updated. The crate's tests + now run using Rust 1.32 or later (MSRV for building the crate has not changed). + by @kjeremy and @bluss + +.. _123: https://github.com/bluss/indexmap/issues/123 +.. _115: https://github.com/bluss/indexmap/pull/115 +.. _120: https://github.com/bluss/indexmap/pull/120 + +- 1.3.2 + + - Maintenance update to regenerate the published `Cargo.toml`. + +- 1.3.1 + + - Maintenance update for formatting and ``autocfg`` 1.0. + +- 1.3.0 + + - The deprecation messages in the previous version have been removed. + (The methods have not otherwise changed.) Docs for removal methods have been + improved. + - From Rust 1.36, this crate supports being built **without std**, requiring + ``alloc`` instead. This is enabled automatically when it is detected that + ``std`` is not available. There is no crate feature to enable/disable to + trigger this. The new build-dep ``autocfg`` enables this. + +- 1.2.0 + + - Plain ``.remove()`` now has a deprecation message, it informs the user + about picking one of the removal functions ``swap_remove`` and ``shift_remove`` + which have different performance and order semantics. + Plain ``.remove()`` will not be removed, the warning message and method + will remain until further. + + - Add new method ``shift_remove`` for order preserving removal on the map, + and ``shift_take`` for the corresponding operation on the set. + + - Add methods ``swap_remove``, ``swap_remove_entry`` to ``Entry``. + + - Fix indexset/indexmap to support full paths, like ``indexmap::indexmap!()`` + + - Internal improvements: fix warnings, deprecations and style lints + +- 1.1.0 + + - Added optional feature `"rayon"` that adds parallel iterator support + to `IndexMap` and `IndexSet` using Rayon. This includes all the regular + iterators in parallel versions, and parallel sort. + + - Implemented ``Clone`` for ``map::{Iter, Keys, Values}`` and + ``set::{Difference, Intersection, Iter, SymmetricDifference, Union}`` + + - Implemented ``Debug`` for ``map::{Entry, IntoIter, Iter, Keys, Values}`` and + ``set::{Difference, Intersection, IntoIter, Iter, SymmetricDifference, Union}`` + + - Serde trait ``IntoDeserializer`` are implemented for ``IndexMap`` and ``IndexSet``. + + - Minimum Rust version requirement increased to Rust 1.30 for development builds. + +- 1.0.2 + + - The new methods ``IndexMap::insert_full`` and ``IndexSet::insert_full`` are + both like ``insert`` with the index included in the return value. + + - The new method ``Entry::and_modify`` can be used to modify occupied + entries, matching the new methods of ``std`` maps in Rust 1.26. + + - The new method ``Entry::or_default`` inserts a default value in unoccupied + entries, matching the new methods of ``std`` maps in Rust 1.28. + +- 1.0.1 + + - Document Rust version policy for the crate (see rustdoc) + +- 1.0.0 + + - This is the 1.0 release for ``indexmap``! (the crate and datastructure + formerly known as “ordermap”) + - ``OccupiedEntry::insert`` changed its signature, to use ``&mut self`` for + the method receiver, matching the equivalent method for a standard + ``HashMap``. Thanks to @dtolnay for finding this bug. + - The deprecated old names from ordermap were removed: ``OrderMap``, + ``OrderSet``, ``ordermap!{}``, ``orderset!{}``. Use the new ``IndexMap`` + etc names instead. + +- 0.4.1 + + - Renamed crate to ``indexmap``; the ``ordermap`` crate is now deprecated + and the types ``OrderMap/Set`` now have a deprecation notice. + +- 0.4.0 + + - This is the last release series for this ``ordermap`` under that name, + because the crate is **going to be renamed** to ``indexmap`` (with types + ``IndexMap``, ``IndexSet``) and no change in functionality! + - The map and its associated structs moved into the ``map`` submodule of the + crate, so that the map and set are symmetric + + + The iterators, ``Entry`` and other structs are now under ``ordermap::map::`` + + - Internally refactored ``OrderMap`` so that all the main algorithms + (insertion, lookup, removal etc) that don't use the ``S`` parameter (the + hasher) are compiled without depending on ``S``, which reduces generics bloat. + + - ``Entry`` no longer has a type parameter ``S``, which is just like + the standard ``HashMap``'s entry. + + - Minimum Rust version requirement increased to Rust 1.18 + +- 0.3.5 + + - Documentation improvements + +- 0.3.4 + + - The ``.retain()`` methods for ``OrderMap`` and ``OrderSet`` now + traverse the elements in order, and the retained elements **keep their order** + - Added new methods ``.sort_by()``, ``.sort_keys()`` to ``OrderMap`` and + ``.sort_by()``, ``.sort()`` to ``OrderSet``. These methods allow you to + sort the maps in place efficiently. + +- 0.3.3 + + - Document insertion behaviour better by @lucab + - Updated dependences (no feature changes) by @ignatenkobrain + +- 0.3.2 + + - Add ``OrderSet`` by @cuviper! + - ``OrderMap::drain`` is now (too) a double ended iterator. + +- 0.3.1 + + - In all ordermap iterators, forward the ``collect`` method to the underlying + iterator as well. + - Add crates.io categories. + +- 0.3.0 + + - The methods ``get_pair``, ``get_pair_index`` were both replaced by + ``get_full`` (and the same for the mutable case). + - Method ``swap_remove_pair`` replaced by ``swap_remove_full``. + - Add trait ``MutableKeys`` for opt-in mutable key access. Mutable key access + is only possible through the methods of this extension trait. + - Add new trait ``Equivalent`` for key equivalence. This extends the + ``Borrow`` trait mechanism for ``OrderMap::get`` in a backwards compatible + way, just some minor type inference related issues may become apparent. + See `#10`__ for more information. + - Implement ``Extend<(&K, &V)>`` by @xfix. + +__ https://github.com/bluss/ordermap/pull/10 + +- 0.2.13 + + - Fix deserialization to support custom hashers by @Techcable. + - Add methods ``.index()`` on the entry types by @garro95. + +- 0.2.12 + + - Add methods ``.with_hasher()``, ``.hasher()``. + +- 0.2.11 + + - Support ``ExactSizeIterator`` for the iterators. By @Binero. + - Use ``Box<[Pos]>`` internally, saving a word in the ``OrderMap`` struct. + - Serde support, with crate feature ``"serde-1"``. By @xfix. + +- 0.2.10 + + - Add iterator ``.drain(..)`` by @stevej. + +- 0.2.9 + + - Add method ``.is_empty()`` by @overvenus. + - Implement ``PartialEq, Eq`` by @overvenus. + - Add method ``.sorted_by()``. + +- 0.2.8 + + - Add iterators ``.values()`` and ``.values_mut()``. + - Fix compatibility with 32-bit platforms. + +- 0.2.7 + + - Add ``.retain()``. + +- 0.2.6 + + - Add ``OccupiedEntry::remove_entry`` and other minor entry methods, + so that it now has all the features of ``HashMap``'s entries. + +- 0.2.5 + + - Improved ``.pop()`` slightly. + +- 0.2.4 + + - Improved performance of ``.insert()`` (`#3`__) by @pczarn. + +__ https://github.com/bluss/ordermap/pull/3 + +- 0.2.3 + + - Generalize ``Entry`` for now, so that it works on hashmaps with non-default + hasher. However, there's a lingering compat issue since libstd ``HashMap`` + does not parameterize its entries by the hasher (``S`` typarm). + - Special case some iterator methods like ``.nth()``. + +- 0.2.2 + + - Disable the verbose ``Debug`` impl by default. + +- 0.2.1 + + - Fix doc links and clarify docs. + +- 0.2.0 + + - Add more ``HashMap`` methods & compat with its API. + - Experimental support for ``.entry()`` (the simplest parts of the API). + - Add ``.reserve()`` (placeholder impl). + - Add ``.remove()`` as synonym for ``.swap_remove()``. + - Changed ``.insert()`` to swap value if the entry already exists, and + return ``Option``. + - Experimental support as an *indexed* hash map! Added methods + ``.get_index()``, ``.get_index_mut()``, ``.swap_remove_index()``, + ``.get_pair_index()``, ``.get_pair_index_mut()``. + +- 0.1.2 + + - Implement the 32/32 split idea for ``Pos`` which improves cache utilization + and lookup performance. + +- 0.1.1 + + - Initial release. From f090281240c05639c665170a2c633c96adfacc07 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 6 Jan 2022 11:10:52 -0800 Subject: [PATCH 036/236] Release 1.7.1 --- Cargo.toml | 2 +- RELEASES.rst | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 01eedc22..50b4efd7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "indexmap" edition = "2018" -version = "1.7.0" +version = "1.7.1" authors = [ "bluss", "Josh Stone " diff --git a/RELEASES.rst b/RELEASES.rst index 5bb6394f..f2024f02 100644 --- a/RELEASES.rst +++ b/RELEASES.rst @@ -1,3 +1,33 @@ +- 1.7.1 + + - The new ``IndexMap::into_keys`` and ``IndexMap::into_values`` will consume + the map into keys or values, respectively, matching Rust 1.54's ``HashMap`` + methods, by @taiki-e in PR 195_. + + - More of the iterator types implement ``Debug``, ``ExactSizeIterator``, and + ``FusedIterator``, by @cuviper in PR 196_. + + - ``IndexMap`` and ``IndexSet`` now implement rayon's ``ParallelDrainRange``, + by @cuviper in PR 197_. + + - ``IndexMap::with_hasher`` and ``IndexSet::with_hasher`` are now ``const`` + functions, allowing static maps and sets, by @mwillsey in PR 203_. + + - ``IndexMap`` and ``IndexSet`` now implement ``From`` for arrays, matching + Rust 1.56's implementation for ``HashMap``, by @rouge8 in PR 205_. + + - ``IndexMap`` and ``IndexSet`` now have methods ``sort_unstable_keys``, + ``sort_unstable_by``, ``sorted_unstable_by``, and ``par_*`` equivalents, + which sort in-place without preserving the order of equal items, by + @bhgomes in PR 211_. + +.. _195: https://github.com/bluss/indexmap/pull/195 +.. _196: https://github.com/bluss/indexmap/pull/196 +.. _197: https://github.com/bluss/indexmap/pull/197 +.. _203: https://github.com/bluss/indexmap/pull/203 +.. _205: https://github.com/bluss/indexmap/pull/205 +.. _211: https://github.com/bluss/indexmap/pull/211 + - 1.7.0 - **MSRV**: Rust 1.49 or later is now required. From 5386d2bf703f48550f9ac6e03c4e28b09cbc689e Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 6 Jan 2022 15:00:49 -0800 Subject: [PATCH 037/236] Release 1.8.0 instead --- Cargo.toml | 2 +- RELEASES.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 50b4efd7..34c4b873 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "indexmap" edition = "2018" -version = "1.7.1" +version = "1.8.0" authors = [ "bluss", "Josh Stone " diff --git a/RELEASES.rst b/RELEASES.rst index f2024f02..c8657e30 100644 --- a/RELEASES.rst +++ b/RELEASES.rst @@ -1,4 +1,4 @@ -- 1.7.1 +- 1.8.0 - The new ``IndexMap::into_keys`` and ``IndexMap::into_values`` will consume the map into keys or values, respectively, matching Rust 1.54's ``HashMap`` From 98de9abe37965efa73daf533fd92b799a5a25947 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Mon, 17 Jan 2022 11:56:36 -0800 Subject: [PATCH 038/236] Update dev-dependencies --- Cargo.toml | 6 +++--- tests/quick.rs | 19 +++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 34c4b873..4f2b30d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,9 +46,9 @@ default-features = false features = ["raw"] [dev-dependencies] -itertools = "0.9" -rand = {version = "0.7", features = ["small_rng"] } -quickcheck = { version = "0.9", default-features = false } +itertools = "0.10" +rand = {version = "0.8", features = ["small_rng"] } +quickcheck = { version = "1.0", default-features = false } fnv = "1.0" lazy_static = "1.3" fxhash = "0.2.1" diff --git a/tests/quick.rs b/tests/quick.rs index 848e14fc..5376e75a 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -6,8 +6,6 @@ use quickcheck::Arbitrary; use quickcheck::Gen; use quickcheck::TestResult; -use rand::Rng; - use fnv::FnvHasher; use std::hash::{BuildHasher, BuildHasherDefault}; type FnvBuilder = BuildHasherDefault; @@ -96,7 +94,8 @@ quickcheck! { true } - fn with_cap(cap: usize) -> bool { + fn with_cap(template: Vec<()>) -> bool { + let cap = template.len(); let map: IndexMap = IndexMap::with_capacity(cap); println!("wish: {}, got: {} (diff: {})", cap, map.capacity(), map.capacity() as isize - cap as isize); map.capacity() >= cap @@ -199,8 +198,8 @@ where K: Arbitrary, V: Arbitrary, { - fn arbitrary(g: &mut G) -> Self { - match g.gen::() % 4 { + fn arbitrary(g: &mut Gen) -> Self { + match u32::arbitrary(g) % 4 { 0 => Add(K::arbitrary(g), V::arbitrary(g)), 1 => AddEntry(K::arbitrary(g), V::arbitrary(g)), 2 => Remove(K::arbitrary(g)), @@ -452,12 +451,12 @@ impl Deref for Alpha { const ALPHABET: &[u8] = b"abcdefghijklmnopqrstuvwxyz"; impl Arbitrary for Alpha { - fn arbitrary(g: &mut G) -> Self { - let len = g.next_u32() % g.size() as u32; + fn arbitrary(g: &mut Gen) -> Self { + let len = usize::arbitrary(g) % g.size(); let len = min(len, 16); Alpha( (0..len) - .map(|_| ALPHABET[g.next_u32() as usize % ALPHABET.len()] as char) + .map(|_| ALPHABET[usize::arbitrary(g) % ALPHABET.len()] as char) .collect(), ) } @@ -482,8 +481,8 @@ impl Arbitrary for Large> where T: Arbitrary, { - fn arbitrary(g: &mut G) -> Self { - let len = g.next_u32() % (g.size() * 10) as u32; + fn arbitrary(g: &mut Gen) -> Self { + let len = usize::arbitrary(g) % (g.size() * 10); Large((0..len).map(|_| T::arbitrary(g)).collect()) } From 4dd6619b4ce609f196c0b521b6382225c348a365 Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Fri, 4 Feb 2022 14:02:12 -0800 Subject: [PATCH 039/236] Add miri builder This adds a builder to run `cargo miri` in order to check for certain classes of undefined behavior. One thing to note is that we've had to parameterize a number of tests to use a smaller cycle count under miri. This is because miri's stacked borrows checker is superlinear: https://github.com/rust-lang/miri/issues/1367 This can cause the miri builder to run out of memory. To avoid this, we reduce the cycle counts for a few tests that are particularly expensive when we're running inside miri. --- .github/workflows/ci.yml | 15 +++++++++++++++ src/map.rs | 2 +- src/set.rs | 2 +- tests/quick.rs | 41 +++++++++++++++++++++++++++++++++++++--- 4 files changed, 55 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b4505705..79cc3d8a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,3 +93,18 @@ jobs: components: clippy - run: cargo clippy + miri: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - nightly + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + components: miri + - run: cargo miri test diff --git a/src/map.rs b/src/map.rs index 9cda12cb..56330470 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1567,7 +1567,7 @@ mod tests { let mut keys = vec![]; keys.extend(0..16); - keys.extend(128..267); + keys.extend(if cfg!(miri) { 32..64 } else { 128..267 }); for &i in &keys { let old_map = map.clone(); diff --git a/src/set.rs b/src/set.rs index be4d7fb4..50b86647 100644 --- a/src/set.rs +++ b/src/set.rs @@ -1426,7 +1426,7 @@ mod tests { let mut values = vec![]; values.extend(0..16); - values.extend(128..267); + values.extend(if cfg!(miri) { 32..64 } else { 128..267 }); for &i in &values { let old_set = set.clone(); diff --git a/tests/quick.rs b/tests/quick.rs index 5376e75a..6c6cc034 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -1,9 +1,9 @@ use indexmap::{IndexMap, IndexSet}; use itertools::Itertools; -use quickcheck::quickcheck; use quickcheck::Arbitrary; use quickcheck::Gen; +use quickcheck::QuickCheck; use quickcheck::TestResult; use fnv::FnvHasher; @@ -39,7 +39,42 @@ where IndexMap::from_iter(iter.into_iter().copied().map(|k| (k, ()))) } -quickcheck! { +// Helper macro to allow us to use smaller quickcheck limits under miri. +macro_rules! quickcheck_limit { + (@as_items $($i:item)*) => ($($i)*); + { + $( + $(#[$m:meta])* + fn $fn_name:ident($($arg_name:ident : $arg_ty:ty),*) -> $ret:ty { + $($code:tt)* + } + )* + } => ( + quickcheck::quickcheck! { + @as_items + $( + #[test] + $(#[$m])* + fn $fn_name() { + fn prop($($arg_name: $arg_ty),*) -> $ret { + $($code)* + } + let mut quickcheck = QuickCheck::new(); + if cfg!(miri) { + quickcheck = quickcheck + .gen(Gen::new(10)) + .tests(10) + .max_tests(100); + } + + quickcheck.quickcheck(prop as fn($($arg_ty),*) -> $ret); + } + )* + } + ) +} + +quickcheck_limit! { fn contains(insert: Vec) -> bool { let mut map = IndexMap::new(); for &key in &insert { @@ -260,7 +295,7 @@ where true } -quickcheck! { +quickcheck_limit! { fn operations_i8(ops: Large>>) -> bool { let mut map = IndexMap::new(); let mut reference = HashMap::new(); From feb816c4b885406fecdac751a480ee1bd269085a Mon Sep 17 00:00:00 2001 From: Zak Cutner Date: Sun, 27 Mar 2022 22:58:49 +0100 Subject: [PATCH 040/236] Add a `replace_full` method on `IndexSet` * Add a new `replace_full` method, which behaves like `replace` but also returns the index of the item. * Clarify that `replace` and `replace_all` do not modify the replaced item's insertion order in the documentation. * Add test coverage for `replace` and `replace_full` by copying the existing tests for `insert` and `insert_full`. --- src/set.rs | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 120 insertions(+), 3 deletions(-) diff --git a/src/set.rs b/src/set.rs index 50b86647..e82a8d07 100644 --- a/src/set.rs +++ b/src/set.rs @@ -396,18 +396,29 @@ where } /// Adds a value to the set, replacing the existing value, if any, that is - /// equal to the given one. Returns the replaced value. + /// equal to the given one, without altering its insertion order. Returns + /// the replaced value. /// /// Computes in **O(1)** time (average). pub fn replace(&mut self, value: T) -> Option { + self.replace_full(value).1 + } + + /// 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 index of the item and its replaced value. + /// + /// Computes in **O(1)** time (average). + pub fn replace_full(&mut self, value: T) -> (usize, Option) { use super::map::Entry::*; match self.map.entry(value) { Vacant(e) => { + let index = e.index(); e.insert(()); - None + (index, None) } - Occupied(e) => Some(e.replace_key()), + Occupied(e) => (e.index(), Some(e.replace_key())), } } @@ -1484,6 +1495,112 @@ mod tests { } } + #[test] + fn replace() { + let replace = [0, 4, 2, 12, 8, 7, 11, 5]; + let not_present = [1, 3, 6, 9, 10]; + let mut set = IndexSet::with_capacity(replace.len()); + + for (i, &elt) in enumerate(&replace) { + assert_eq!(set.len(), i); + set.replace(elt); + assert_eq!(set.len(), i + 1); + assert_eq!(set.get(&elt), Some(&elt)); + } + println!("{:?}", set); + + for &elt in ¬_present { + assert!(set.get(&elt).is_none()); + } + } + + #[test] + fn replace_full() { + let replace = vec![9, 2, 7, 1, 4, 6, 13]; + let present = vec![1, 6, 2]; + let mut set = IndexSet::with_capacity(replace.len()); + + for (i, &elt) in enumerate(&replace) { + assert_eq!(set.len(), i); + let (index, replaced) = set.replace_full(elt); + assert!(replaced.is_none()); + assert_eq!(Some(index), set.get_full(&elt).map(|x| x.0)); + assert_eq!(set.len(), i + 1); + } + + let len = set.len(); + for &elt in &present { + let (index, replaced) = set.replace_full(elt); + assert_eq!(Some(elt), replaced); + assert_eq!(Some(index), set.get_full(&elt).map(|x| x.0)); + assert_eq!(set.len(), len); + } + } + + #[test] + fn replace_2() { + let mut set = IndexSet::with_capacity(16); + + let mut values = vec![]; + values.extend(0..16); + values.extend(if cfg!(miri) { 32..64 } else { 128..267 }); + + for &i in &values { + let old_set = set.clone(); + set.replace(i); + for value in old_set.iter() { + if set.get(value).is_none() { + println!("old_set: {:?}", old_set); + println!("set: {:?}", set); + panic!("did not find {} in set", value); + } + } + } + + for &i in &values { + assert!(set.get(&i).is_some(), "did not find {}", i); + } + } + + #[test] + fn replace_dup() { + let mut elements = vec![0, 2, 4, 6, 8]; + let mut set: IndexSet = elements.drain(..).collect(); + { + let (i, v) = set.get_full(&0).unwrap(); + assert_eq!(set.len(), 5); + assert_eq!(i, 0); + assert_eq!(*v, 0); + } + { + let replaced = set.replace(0); + let (i, v) = set.get_full(&0).unwrap(); + assert_eq!(set.len(), 5); + assert_eq!(replaced, Some(0)); + assert_eq!(i, 0); + assert_eq!(*v, 0); + } + } + + #[test] + fn replace_order() { + let replace = [0, 4, 2, 12, 8, 7, 11, 5, 3, 17, 19, 22, 23]; + let mut set = IndexSet::new(); + + for &elt in &replace { + set.replace(elt); + } + + assert_eq!(set.iter().count(), set.len()); + assert_eq!(set.iter().count(), replace.len()); + for (a, b) in replace.iter().zip(set.iter()) { + assert_eq!(a, b); + } + for (i, v) in (0..replace.len()).zip(set.iter()) { + assert_eq!(set.get_index(i).unwrap(), v); + } + } + #[test] fn grow() { let insert = [0, 4, 2, 12, 8, 7, 11]; From 12162abeb0924c0a48c4bb89c12a9bc84460973c Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 29 Mar 2022 15:45:07 -0700 Subject: [PATCH 041/236] Release 1.8.1 --- Cargo.toml | 2 +- RELEASES.rst | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4f2b30d2..e330b13f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "indexmap" edition = "2018" -version = "1.8.0" +version = "1.8.1" authors = [ "bluss", "Josh Stone " diff --git a/RELEASES.rst b/RELEASES.rst index c8657e30..402724a2 100644 --- a/RELEASES.rst +++ b/RELEASES.rst @@ -1,3 +1,10 @@ +- 1.8.1 + + - The new ``IndexSet::replace_full`` will return the index of the item along + with the replaced value, if any, by @zakcutner in PR 222_. + +.. _222: https://github.com/bluss/indexmap/pull/222 + - 1.8.0 - The new ``IndexMap::into_keys`` and ``IndexMap::into_values`` will consume From 61bb73ed5d5b6c092a7ffe43f193db38497c1df2 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 8 Feb 2022 10:59:30 -0800 Subject: [PATCH 042/236] Prepare for indexmap 2.0.0 --- .github/workflows/ci.yml | 4 ++-- .rustfmt.toml | 2 +- Cargo.toml | 29 +++++++---------------------- README.rst | 4 ++-- src/lib.rs | 6 +++--- test-nostd/Cargo.toml | 3 +-- test-serde/Cargo.toml | 3 +-- 7 files changed, 17 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 79cc3d8a..739d52cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: include: - - rust: 1.49.0 # MSRV + - rust: 1.56.0 # MSRV features: - rust: stable features: serde @@ -59,7 +59,7 @@ jobs: strategy: matrix: include: - - rust: 1.49.0 + - rust: 1.56.0 target: thumbv6m-none-eabi - rust: stable target: thumbv6m-none-eabi diff --git a/.rustfmt.toml b/.rustfmt.toml index 32a9786f..3a26366d 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1 +1 @@ -edition = "2018" +edition = "2021" diff --git a/Cargo.toml b/Cargo.toml index e330b13f..adcd51a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,37 +1,22 @@ [package] name = "indexmap" -edition = "2018" -version = "1.8.1" -authors = [ -"bluss", -"Josh Stone " -] +edition = "2021" +version = "2.0.0-pre" +publish = false documentation = "https://docs.rs/indexmap/" repository = "https://github.com/bluss/indexmap" -license = "Apache-2.0/MIT" -description = """ -A hash table with consistent order and fast iteration. - -The indexmap is a hash table where the iteration order of the key-value -pairs is independent of the hash values of the keys. It has the usual -hash table functionality, it preserves insertion order except after -removals, and it allows lookup of its elements by either hash table key -or numerical index. A corresponding hash set type is also provided. - -This crate was initially published under the name ordermap, but it was renamed to -indexmap. -""" - +license = "Apache-2.0 OR MIT" +description = "A hash table with consistent order and fast iteration." keywords = ["hashmap", "no_std"] categories = ["data-structures", "no-std"] - -build = "build.rs" +rust-version = "1.56" [lib] bench = false [build-dependencies] autocfg = "1" + [dependencies] serde = { version = "1.0", optional = true, default-features = false } rayon = { version = "1.4.1", optional = true } diff --git a/README.rst b/README.rst index da76a68b..4dac24f4 100644 --- a/README.rst +++ b/README.rst @@ -12,8 +12,8 @@ indexmap .. |docs| image:: https://docs.rs/indexmap/badge.svg .. _docs: https://docs.rs/indexmap -.. |rustc| image:: https://img.shields.io/badge/rust-1.49%2B-orange.svg -.. _rustc: https://img.shields.io/badge/rust-1.49%2B-orange.svg +.. |rustc| image:: https://img.shields.io/badge/rust-1.56%2B-orange.svg +.. _rustc: https://img.shields.io/badge/rust-1.56%2B-orange.svg A pure-Rust hash table which preserves (in a limited sense) insertion order. diff --git a/src/lib.rs b/src/lib.rs index 8c44d7bf..2ef1fb9f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,10 +53,10 @@ //! //! ### Rust Version //! -//! This version of indexmap requires Rust 1.49 or later. +//! This version of indexmap requires Rust 1.56 or later. //! -//! The indexmap 1.x release series will use a carefully considered version -//! upgrade policy, where in a later 1.x version, we will raise the minimum +//! The indexmap 2.x release series will use a carefully considered version +//! upgrade policy, where in a later 2.x version, we will raise the minimum //! required Rust version. //! //! ## No Standard Library Targets diff --git a/test-nostd/Cargo.toml b/test-nostd/Cargo.toml index 0201a728..3ae606d5 100644 --- a/test-nostd/Cargo.toml +++ b/test-nostd/Cargo.toml @@ -1,9 +1,8 @@ [package] name = "test-nostd" version = "0.1.0" -authors = ["bluss"] publish = false -edition = "2018" +edition = "2021" [dependencies] indexmap = { path = "..", features = ["serde-1"] } diff --git a/test-serde/Cargo.toml b/test-serde/Cargo.toml index f7abc9e5..791c0a38 100644 --- a/test-serde/Cargo.toml +++ b/test-serde/Cargo.toml @@ -1,9 +1,8 @@ [package] name = "test-serde" version = "0.1.0" -authors = ["bluss"] publish = false -edition = "2018" +edition = "2021" [dependencies] From c3d7c396b6da6fe04f5607924998bd2a6e3435a1 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 8 Feb 2022 11:04:22 -0800 Subject: [PATCH 043/236] Remove unused feature test_low_transition_point --- .github/workflows/ci.yml | 2 -- Cargo.toml | 1 - 2 files changed, 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 739d52cf..f04cce5a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,8 +30,6 @@ jobs: features: - rust: nightly bench: test build benchmarks - - rust: nightly - features: test_low_transition_point steps: - uses: actions/checkout@v2 diff --git a/Cargo.toml b/Cargo.toml index adcd51a9..ce260949 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,6 @@ serde-1 = ["serde"] std = [] # for testing only, of course -test_low_transition_point = [] test_debug = [] [profile.bench] From ed9bf13aaf7c46e7ef3332009c0f591fec667e72 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 8 Feb 2022 11:09:58 -0800 Subject: [PATCH 044/236] Remove redundant feature serde-1 --- Cargo.toml | 5 +---- src/serde.rs | 8 ++++---- src/serde_seq.rs | 6 +++--- test-nostd/Cargo.toml | 2 +- test-serde/Cargo.toml | 2 +- 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ce260949..dec5b01e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,9 +40,6 @@ fxhash = "0.2.1" serde_derive = "1.0" [features] -# Serialization with serde 1.0 -serde-1 = ["serde"] - # Force the use of `std`, bypassing target detection. std = [] @@ -57,7 +54,7 @@ no-dev-version = true tag-name = "{{version}}" [package.metadata.docs.rs] -features = ["serde-1", "rayon"] +features = ["serde", "rayon"] [workspace] members = ["test-nostd", "test-serde"] diff --git a/src/serde.rs b/src/serde.rs index c6dd6d5e..d7473d39 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -10,7 +10,7 @@ use core::marker::PhantomData; use crate::IndexMap; -/// Requires crate feature `"serde"` or `"serde-1"` +/// Requires crate feature `"serde"` impl Serialize for IndexMap where K: Serialize + Hash + Eq, @@ -54,7 +54,7 @@ where } } -/// Requires crate feature `"serde"` or `"serde-1"` +/// Requires crate feature `"serde"` impl<'de, K, V, S> Deserialize<'de> for IndexMap where K: Deserialize<'de> + Eq + Hash, @@ -85,7 +85,7 @@ where use crate::IndexSet; -/// Requires crate feature `"serde"` or `"serde-1"` +/// Requires crate feature `"serde"` impl Serialize for IndexSet where T: Serialize + Hash + Eq, @@ -127,7 +127,7 @@ where } } -/// Requires crate feature `"serde"` or `"serde-1"` +/// Requires crate feature `"serde"` impl<'de, T, S> Deserialize<'de> for IndexSet where T: Deserialize<'de> + Eq + Hash, diff --git a/src/serde_seq.rs b/src/serde_seq.rs index d326a02e..7c89a6e5 100644 --- a/src/serde_seq.rs +++ b/src/serde_seq.rs @@ -18,7 +18,7 @@ //! } //! ``` //! -//! Requires crate feature `"serde"` or `"serde-1"` +//! Requires crate feature `"serde"` use serde::de::{Deserialize, Deserializer, SeqAccess, Visitor}; use serde::ser::{Serialize, Serializer}; @@ -44,7 +44,7 @@ use crate::IndexMap; /// } /// ``` /// -/// Requires crate feature `"serde"` or `"serde-1"` +/// Requires crate feature `"serde"` pub fn serialize(map: &IndexMap, serializer: T) -> Result where K: Serialize + Hash + Eq, @@ -100,7 +100,7 @@ where /// } /// ``` /// -/// Requires crate feature `"serde"` or `"serde-1"` +/// Requires crate feature `"serde"` pub fn deserialize<'de, D, K, V, S>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, diff --git a/test-nostd/Cargo.toml b/test-nostd/Cargo.toml index 3ae606d5..e926762d 100644 --- a/test-nostd/Cargo.toml +++ b/test-nostd/Cargo.toml @@ -5,6 +5,6 @@ publish = false edition = "2021" [dependencies] -indexmap = { path = "..", features = ["serde-1"] } +indexmap = { path = "..", features = ["serde"] } [dev-dependencies] diff --git a/test-serde/Cargo.toml b/test-serde/Cargo.toml index 791c0a38..bf04c9fa 100644 --- a/test-serde/Cargo.toml +++ b/test-serde/Cargo.toml @@ -8,6 +8,6 @@ edition = "2021" [dev-dependencies] fnv = "1.0" -indexmap = { path = "..", features = ["serde-1"] } +indexmap = { path = "..", features = ["serde"] } serde = { version = "1.0.99", features = ["derive"] } serde_test = "1.0.99" From 99848fff179911f2573011dfc2859c182c9c0cfb Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 8 Feb 2022 11:19:17 -0800 Subject: [PATCH 045/236] Always use an explicit feature for std --- Cargo.toml | 5 +---- build.rs | 9 --------- src/lib.rs | 11 +++++------ src/macros.rs | 4 ++-- src/map.rs | 12 ++++++------ src/set.rs | 12 ++++++------ test-nostd/Cargo.toml | 6 ++++-- 7 files changed, 24 insertions(+), 35 deletions(-) delete mode 100644 build.rs diff --git a/Cargo.toml b/Cargo.toml index dec5b01e..f5ed534d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,9 +14,6 @@ rust-version = "1.56" [lib] bench = false -[build-dependencies] -autocfg = "1" - [dependencies] serde = { version = "1.0", optional = true, default-features = false } rayon = { version = "1.4.1", optional = true } @@ -40,7 +37,7 @@ fxhash = "0.2.1" serde_derive = "1.0" [features] -# Force the use of `std`, bypassing target detection. +default = ["std"] std = [] # for testing only, of course diff --git a/build.rs b/build.rs deleted file mode 100644 index 7c5b6d5e..00000000 --- a/build.rs +++ /dev/null @@ -1,9 +0,0 @@ -fn main() { - // If "std" is explicitly requested, don't bother probing the target for it. - match std::env::var_os("CARGO_FEATURE_STD") { - Some(_) => autocfg::emit("has_std"), - None => autocfg::new().emit_sysroot_crate("std"), - } - autocfg::new().emit_rustc_version(1, 51); - autocfg::rerun_path("build.rs"); -} diff --git a/src/lib.rs b/src/lib.rs index 2ef1fb9f..3d796ea7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,13 +61,12 @@ //! //! ## No Standard Library Targets //! -//! This crate supports being built without `std`, requiring -//! `alloc` instead. This is enabled automatically when it is detected that -//! `std` is not available. There is no crate feature to enable/disable to -//! trigger this. It can be tested by building for a std-less target. +//! This crate supports being built without `std`, requiring `alloc` instead. +//! This is chosen by disabling the default "std" cargo feature, by adding +//! `default-features = false` to your dependency specification. //! //! - Creating maps and sets using [`new`][IndexMap::new] and -//! [`with_capacity`][IndexMap::with_capacity] is unavailable without `std`. +//! [`with_capacity`][IndexMap::with_capacity] is unavailable without `std`. //! Use methods [`IndexMap::default`][def], //! [`with_hasher`][IndexMap::with_hasher], //! [`with_capacity_and_hasher`][IndexMap::with_capacity_and_hasher] instead. @@ -79,7 +78,7 @@ extern crate alloc; -#[cfg(has_std)] +#[cfg(feature = "std")] #[macro_use] extern crate std; diff --git a/src/macros.rs b/src/macros.rs index ca26287b..20ab58db 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,4 +1,4 @@ -#[cfg(has_std)] +#[cfg(feature = "std")] #[macro_export] /// Create an `IndexMap` from a list of key-value pairs /// @@ -35,7 +35,7 @@ macro_rules! indexmap { }; } -#[cfg(has_std)] +#[cfg(feature = "std")] #[macro_export] /// Create an `IndexSet` from a list of values /// diff --git a/src/map.rs b/src/map.rs index 56330470..01bdeb3e 100644 --- a/src/map.rs +++ b/src/map.rs @@ -16,7 +16,7 @@ use ::core::iter::{FromIterator, FusedIterator}; use ::core::ops::{Index, IndexMut, RangeBounds}; use ::core::slice::{Iter as SliceIter, IterMut as SliceIterMut}; -#[cfg(has_std)] +#[cfg(feature = "std")] use std::collections::hash_map::RandomState; use self::core::IndexMapCore; @@ -67,12 +67,12 @@ pub use self::core::{Entry, OccupiedEntry, VacantEntry}; /// assert_eq!(letters[&'u'], 1); /// assert_eq!(letters.get(&'y'), None); /// ``` -#[cfg(has_std)] +#[cfg(feature = "std")] pub struct IndexMap { pub(crate) core: IndexMapCore, hash_builder: S, } -#[cfg(not(has_std))] +#[cfg(not(feature = "std"))] pub struct IndexMap { pub(crate) core: IndexMapCore, hash_builder: S, @@ -140,7 +140,7 @@ where } } -#[cfg(has_std)] +#[cfg(feature = "std")] impl IndexMap { /// Create a new map. (Does not allocate.) #[inline] @@ -1391,7 +1391,7 @@ where } } -#[cfg(all(has_std, rustc_1_51))] +#[cfg(feature = "std")] impl From<[(K, V); N]> for IndexMap where K: Hash + Eq, @@ -1906,7 +1906,7 @@ mod tests { } #[test] - #[cfg(all(has_std, rustc_1_51))] + #[cfg(feature = "std")] fn from_array() { let map = IndexMap::from([(1, 2), (3, 4)]); let mut expected = IndexMap::new(); diff --git a/src/set.rs b/src/set.rs index e82a8d07..02253ce3 100644 --- a/src/set.rs +++ b/src/set.rs @@ -3,7 +3,7 @@ #[cfg(feature = "rayon")] pub use crate::rayon::set as rayon; -#[cfg(has_std)] +#[cfg(feature = "std")] use std::collections::hash_map::RandomState; use crate::vec::{self, Vec}; @@ -59,11 +59,11 @@ type Bucket = super::Bucket; /// assert!(letters.contains(&'u')); /// assert!(!letters.contains(&'y')); /// ``` -#[cfg(has_std)] +#[cfg(feature = "std")] pub struct IndexSet { pub(crate) map: IndexMap, } -#[cfg(not(has_std))] +#[cfg(not(feature = "std"))] pub struct IndexSet { pub(crate) map: IndexMap, } @@ -124,7 +124,7 @@ where } } -#[cfg(has_std)] +#[cfg(feature = "std")] impl IndexSet { /// Create a new set. (Does not allocate.) pub fn new() -> Self { @@ -888,7 +888,7 @@ where } } -#[cfg(all(has_std, rustc_1_51))] +#[cfg(feature = "std")] impl From<[T; N]> for IndexSet where T: Eq + Hash, @@ -1882,7 +1882,7 @@ mod tests { } #[test] - #[cfg(all(has_std, rustc_1_51))] + #[cfg(feature = "std")] fn from_array() { let set1 = IndexSet::from([1, 2, 3, 4]); let set2: IndexSet<_> = [1, 2, 3, 4].into(); diff --git a/test-nostd/Cargo.toml b/test-nostd/Cargo.toml index e926762d..64d209a9 100644 --- a/test-nostd/Cargo.toml +++ b/test-nostd/Cargo.toml @@ -4,7 +4,9 @@ version = "0.1.0" publish = false edition = "2021" -[dependencies] -indexmap = { path = "..", features = ["serde"] } +[dependencies.indexmap] +path = ".." +default-features = false +features = ["serde"] [dev-dependencies] From ffd875b1c03e54299948f2c015965c679ca8737e Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 8 Feb 2022 11:22:43 -0800 Subject: [PATCH 046/236] Use prelude FromIterator --- benches/bench.rs | 1 - benches/faststring.rs | 1 - src/map.rs | 4 ++-- src/set.rs | 4 ++-- test-nostd/src/lib.rs | 1 - tests/quick.rs | 1 - 6 files changed, 4 insertions(+), 8 deletions(-) diff --git a/benches/bench.rs b/benches/bench.rs index d6de602d..a4e8e21b 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -15,7 +15,6 @@ use test::Bencher; use indexmap::IndexMap; use std::collections::HashMap; -use std::iter::FromIterator; use rand::rngs::SmallRng; use rand::seq::SliceRandom; diff --git a/benches/faststring.rs b/benches/faststring.rs index 86b7e9cf..ecc28b40 100644 --- a/benches/faststring.rs +++ b/benches/faststring.rs @@ -7,7 +7,6 @@ use test::Bencher; use indexmap::IndexMap; use std::collections::HashMap; -use std::iter::FromIterator; use rand::rngs::SmallRng; use rand::seq::SliceRandom; diff --git a/src/map.rs b/src/map.rs index 01bdeb3e..62346ba9 100644 --- a/src/map.rs +++ b/src/map.rs @@ -12,7 +12,7 @@ use crate::vec::{self, Vec}; use ::core::cmp::Ordering; use ::core::fmt; use ::core::hash::{BuildHasher, Hash, Hasher}; -use ::core::iter::{FromIterator, FusedIterator}; +use ::core::iter::FusedIterator; use ::core::ops::{Index, IndexMut, RangeBounds}; use ::core::slice::{Iter as SliceIter, IterMut as SliceIterMut}; @@ -1406,7 +1406,7 @@ where /// assert_eq!(map1, map2); /// ``` fn from(arr: [(K, V); N]) -> Self { - std::array::IntoIter::new(arr).collect() + Self::from_iter(arr) } } diff --git a/src/set.rs b/src/set.rs index 02253ce3..cff168c8 100644 --- a/src/set.rs +++ b/src/set.rs @@ -10,7 +10,7 @@ use crate::vec::{self, Vec}; use core::cmp::Ordering; use core::fmt; use core::hash::{BuildHasher, Hash}; -use core::iter::{Chain, FromIterator, FusedIterator}; +use core::iter::{Chain, FusedIterator}; use core::ops::{BitAnd, BitOr, BitXor, Index, RangeBounds, Sub}; use core::slice; @@ -903,7 +903,7 @@ where /// assert_eq!(set1, set2); /// ``` fn from(arr: [T; N]) -> Self { - std::array::IntoIter::new(arr).collect() + Self::from_iter(arr) } } diff --git a/test-nostd/src/lib.rs b/test-nostd/src/lib.rs index 0b57b092..27bfe608 100644 --- a/test-nostd/src/lib.rs +++ b/test-nostd/src/lib.rs @@ -2,7 +2,6 @@ use core::hash::BuildHasherDefault; use core::hash::Hasher; -use core::iter::FromIterator; use indexmap::IndexMap; use indexmap::IndexSet; diff --git a/tests/quick.rs b/tests/quick.rs index 6c6cc034..4f462878 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -16,7 +16,6 @@ use std::collections::HashMap; use std::collections::HashSet; use std::fmt::Debug; use std::hash::Hash; -use std::iter::FromIterator; use std::ops::Bound; use std::ops::Deref; From 8eeea2ce0cbbfb52bc1c1ac9acfab26336e500e0 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 8 Feb 2022 12:06:55 -0800 Subject: [PATCH 047/236] Upgrade to hashbrown 0.12 --- Cargo.toml | 2 +- src/map/core.rs | 24 ++++++------------------ src/map/core/raw.rs | 15 ++++++++++++++- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f5ed534d..85867a1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ rayon = { version = "1.4.1", optional = true } rustc-rayon = { version = "0.3", optional = true } [dependencies.hashbrown] -version = "0.11" +version = "0.12" default-features = false features = ["raw"] diff --git a/src/map/core.rs b/src/map/core.rs index bfd571d9..0ed9b1ff 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -18,7 +18,7 @@ use core::mem::replace; use core::ops::RangeBounds; use crate::equivalent::Equivalent; -use crate::util::{enumerate, simplify_range}; +use crate::util::simplify_range; use crate::{Bucket, Entries, HashValue}; /// Core of the map that does not depend on S @@ -185,9 +185,7 @@ impl IndexMapCore { let entries = self.entries.split_off(at); let mut indices = RawTable::with_capacity(entries.len()); - for (i, entry) in enumerate(&entries) { - indices.insert_no_grow(entry.hash.get(), i); - } + raw::insert_bulk_no_grow(&mut indices, &entries); Self { indices, entries } } @@ -372,15 +370,9 @@ impl IndexMapCore { // Reinsert everything, as there are few kept indices self.indices.clear(); - // Reinsert stable indices - for (i, entry) in enumerate(start_entries) { - self.indices.insert_no_grow(entry.hash.get(), i); - } - - // Reinsert shifted indices - for (i, entry) in (start..).zip(shifted_entries) { - self.indices.insert_no_grow(entry.hash.get(), i); - } + // Reinsert stable indices, then shifted indices + raw::insert_bulk_no_grow(&mut self.indices, start_entries); + raw::insert_bulk_no_grow(&mut self.indices, shifted_entries); } else if erased + shifted < half_capacity { // Find each affected index, as there are few to adjust @@ -429,11 +421,7 @@ impl IndexMapCore { fn rebuild_hash_table(&mut self) { self.indices.clear(); - debug_assert!(self.indices.capacity() >= self.entries.len()); - for (i, entry) in enumerate(&self.entries) { - // We should never have to reallocate, so there's no need for a real hasher. - self.indices.insert_no_grow(entry.hash.get(), i); - } + raw::insert_bulk_no_grow(&mut self.indices, &self.entries); } pub(crate) fn reverse(&mut self) { diff --git a/src/map/core/raw.rs b/src/map/core/raw.rs index 168e1af3..bf1672d5 100644 --- a/src/map/core/raw.rs +++ b/src/map/core/raw.rs @@ -2,13 +2,26 @@ //! This module encapsulates the `unsafe` access to `hashbrown::raw::RawTable`, //! mostly in dealing with its bucket "pointers". -use super::{equivalent, Entry, HashValue, IndexMapCore, VacantEntry}; +use super::{equivalent, Bucket, Entry, HashValue, IndexMapCore, VacantEntry}; use core::fmt; use core::mem::replace; use hashbrown::raw::RawTable; type RawBucket = hashbrown::raw::Bucket; +/// Inserts many entries into a raw table without reallocating. +/// +/// ***Panics*** if there is not sufficient capacity already. +pub(super) fn insert_bulk_no_grow(indices: &mut RawTable, entries: &[Bucket]) { + assert!(indices.capacity() - indices.len() >= entries.len()); + for entry in entries { + // SAFETY: we asserted that sufficient capacity exists for all entries. + unsafe { + indices.insert_no_grow(entry.hash.get(), indices.len()); + } + } +} + pub(super) struct DebugIndices<'a>(pub &'a RawTable); impl fmt::Debug for DebugIndices<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { From bdd93a379955a250c4629e86cc46ec2ae2848ebf Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 8 Feb 2022 12:07:08 -0800 Subject: [PATCH 048/236] Remove crate::util::enumerate --- src/map.rs | 7 +++---- src/set.rs | 11 +++++------ src/util.rs | 8 -------- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/map.rs b/src/map.rs index 62346ba9..932a3b2d 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1495,7 +1495,6 @@ where #[cfg(test)] mod tests { use super::*; - use crate::util::enumerate; use std::string::String; #[test] @@ -1524,7 +1523,7 @@ mod tests { let not_present = [1, 3, 6, 9, 10]; let mut map = IndexMap::with_capacity(insert.len()); - for (i, &elt) in enumerate(&insert) { + for (i, &elt) in insert.iter().enumerate() { assert_eq!(map.len(), i); map.insert(elt, elt); assert_eq!(map.len(), i + 1); @@ -1544,7 +1543,7 @@ mod tests { let present = vec![1, 6, 2]; let mut map = IndexMap::with_capacity(insert.len()); - for (i, &elt) in enumerate(&insert) { + for (i, &elt) in insert.iter().enumerate() { assert_eq!(map.len(), i); let (index, existing) = map.insert_full(elt, elt); assert_eq!(existing, None); @@ -1611,7 +1610,7 @@ mod tests { let not_present = [1, 3, 6, 9, 10]; let mut map = IndexMap::with_capacity(insert.len()); - for (i, &elt) in enumerate(&insert) { + for (i, &elt) in insert.iter().enumerate() { assert_eq!(map.len(), i); map.insert(elt, elt); assert_eq!(map.len(), i + 1); diff --git a/src/set.rs b/src/set.rs index cff168c8..387168d4 100644 --- a/src/set.rs +++ b/src/set.rs @@ -1366,7 +1366,6 @@ where #[cfg(test)] mod tests { use super::*; - use crate::util::enumerate; use std::string::String; #[test] @@ -1395,7 +1394,7 @@ mod tests { let not_present = [1, 3, 6, 9, 10]; let mut set = IndexSet::with_capacity(insert.len()); - for (i, &elt) in enumerate(&insert) { + for (i, &elt) in insert.iter().enumerate() { assert_eq!(set.len(), i); set.insert(elt); assert_eq!(set.len(), i + 1); @@ -1414,7 +1413,7 @@ mod tests { let present = vec![1, 6, 2]; let mut set = IndexSet::with_capacity(insert.len()); - for (i, &elt) in enumerate(&insert) { + for (i, &elt) in insert.iter().enumerate() { assert_eq!(set.len(), i); let (index, success) = set.insert_full(elt); assert!(success); @@ -1501,7 +1500,7 @@ mod tests { let not_present = [1, 3, 6, 9, 10]; let mut set = IndexSet::with_capacity(replace.len()); - for (i, &elt) in enumerate(&replace) { + for (i, &elt) in replace.iter().enumerate() { assert_eq!(set.len(), i); set.replace(elt); assert_eq!(set.len(), i + 1); @@ -1520,7 +1519,7 @@ mod tests { let present = vec![1, 6, 2]; let mut set = IndexSet::with_capacity(replace.len()); - for (i, &elt) in enumerate(&replace) { + for (i, &elt) in replace.iter().enumerate() { assert_eq!(set.len(), i); let (index, replaced) = set.replace_full(elt); assert!(replaced.is_none()); @@ -1607,7 +1606,7 @@ mod tests { let not_present = [1, 3, 6, 9, 10]; let mut set = IndexSet::with_capacity(insert.len()); - for (i, &elt) in enumerate(&insert) { + for (i, &elt) in insert.iter().enumerate() { assert_eq!(set.len(), i); set.insert(elt); assert_eq!(set.len(), i + 1); diff --git a/src/util.rs b/src/util.rs index 5388f470..a24dfafd 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,17 +1,9 @@ -use core::iter::Enumerate; use core::ops::{Bound, Range, RangeBounds}; pub(crate) fn third(t: (A, B, C)) -> C { t.2 } -pub(crate) fn enumerate(iterable: I) -> Enumerate -where - I: IntoIterator, -{ - iterable.into_iter().enumerate() -} - pub(crate) fn simplify_range(range: R, len: usize) -> Range where R: RangeBounds, From 931aaf580dd0dffb90e3e19fb77a8f3937f68f3b Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 8 Feb 2022 12:20:31 -0800 Subject: [PATCH 049/236] Make get_index_mut return &K, and add get_index_mut2 --- src/map.rs | 19 ++----------------- src/mutable_keys.rs | 24 ++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/map.rs b/src/map.rs index 932a3b2d..4c6a0229 100644 --- a/src/map.rs +++ b/src/map.rs @@ -479,21 +479,6 @@ where } } - pub(crate) fn get_full_mut2_impl( - &mut self, - key: &Q, - ) -> Option<(usize, &mut K, &mut V)> - where - Q: Hash + Equivalent, - { - if let Some(i) = self.get_index_of(key) { - let entry = &mut self.as_entries_mut()[i]; - Some((i, &mut entry.key, &mut entry.value)) - } else { - None - } - } - /// Remove the key-value pair equivalent to `key` and return /// its value. /// @@ -780,8 +765,8 @@ impl IndexMap { /// Valid indices are *0 <= index < self.len()* /// /// Computes in **O(1)** time. - pub fn get_index_mut(&mut self, index: usize) -> Option<(&mut K, &mut V)> { - self.as_entries_mut().get_mut(index).map(Bucket::muts) + pub fn get_index_mut(&mut self, index: usize) -> Option<(&K, &mut V)> { + self.as_entries_mut().get_mut(index).map(Bucket::ref_mut) } /// Get the first key-value pair diff --git a/src/mutable_keys.rs b/src/mutable_keys.rs index 35a90c47..fad7d60f 100644 --- a/src/mutable_keys.rs +++ b/src/mutable_keys.rs @@ -1,6 +1,6 @@ use core::hash::{BuildHasher, Hash}; -use super::{Equivalent, IndexMap}; +use super::{Bucket, Entries, Equivalent, IndexMap}; pub struct PrivateMarker {} @@ -21,6 +21,8 @@ pub trait MutableKeys { type Value; /// Return item index, mutable reference to key and value + /// + /// Computes in **O(1)** time (average). fn get_full_mut2( &mut self, key: &Q, @@ -28,6 +30,13 @@ pub trait MutableKeys { where Q: Hash + Equivalent; + /// Return mutable reference to key and value at an index. + /// + /// Valid indices are *0 <= index < self.len()* + /// + /// Computes in **O(1)** time. + fn get_index_mut2(&mut self, index: usize) -> Option<(&mut Self::Key, &mut Self::Value)>; + /// Scan through each key-value pair in the map and keep those where the /// closure `keep` returns `true`. /// @@ -39,6 +48,7 @@ pub trait MutableKeys { where F: FnMut(&mut Self::Key, &mut Self::Value) -> bool; + #[doc(hidden)] /// This method is not useful in itself – it is there to “seal” the trait /// for external implementation, so that we can add methods without /// causing breaking changes. @@ -55,11 +65,21 @@ where { type Key = K; type Value = V; + fn get_full_mut2(&mut self, key: &Q) -> Option<(usize, &mut K, &mut V)> where Q: Hash + Equivalent, { - self.get_full_mut2_impl(key) + if let Some(i) = self.get_index_of(key) { + let entry = &mut self.as_entries_mut()[i]; + Some((i, &mut entry.key, &mut entry.value)) + } else { + None + } + } + + fn get_index_mut2(&mut self, index: usize) -> Option<(&mut K, &mut V)> { + self.as_entries_mut().get_mut(index).map(Bucket::muts) } fn retain2(&mut self, keep: F) From bdba2a65005bc22d414720c19ab9999ddfec236e Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 8 Feb 2022 13:36:29 -0800 Subject: [PATCH 050/236] Convert the README to markdown --- README.md | 55 +++++++++++++++++++++++++++++++++++++++++++ README.rst | 69 ------------------------------------------------------ 2 files changed, 55 insertions(+), 69 deletions(-) create mode 100644 README.md delete mode 100644 README.rst diff --git a/README.md b/README.md new file mode 100644 index 00000000..a227e23d --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# indexmap + +[![build status](https://github.com/bluss/indexmap/workflows/Continuous%20integration/badge.svg?branch=master)](https://github.com/bluss/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.56%2B-orange.svg)](https://img.shields.io/badge/rust-1.56%2B-orange.svg) + +A pure-Rust hash table which preserves (in a limited sense) insertion order. + +This crate implements compact map and set data-structures, +where the iteration order of the keys is independent from their hash or +value. It preserves insertion order (except after removals), and it +allows lookup of entries by either hash table key or numerical index. + +Note: this crate was originally released under the name `ordermap`, +but it was renamed to `indexmap` to better reflect its features. + +# Background + +This was inspired by Python 3.6's new dict implementation (which remembers +the insertion order and is fast to iterate, and is compact in memory). + +Some of those features were translated to Rust, and some were not. The result +was indexmap, a hash table that has following properties: + +- Order is **independent of hash function** and hash values of keys. +- Fast to iterate. +- Indexed in compact space. +- Preserves insertion order **as long** as you don't call `.remove()`. +- Uses hashbrown for the inner table, just like Rust's libstd `HashMap` does. + +## Performance + +`IndexMap` derives a couple of performance facts directly from how it is constructed, +which is roughly: + +> A raw hash table of key-value indices, and a vector of key-value pairs. + +- Iteration is very fast since it is on the dense key-values. +- Removal is fast since it moves memory areas only in the table, + and uses a single swap in the vector. +- Lookup is fast-ish because the initial 7-bit hash lookup uses SIMD, and indices are + densely stored. Lookup also is slow-ish since the actual key-value pairs are stored + separately. (Visible when cpu caches size is limiting.) + +- In practice, `IndexMap` has been tested out as the hashmap in rustc in [PR45282] and + the performance was roughly on par across the whole workload. +- If you want the properties of `IndexMap`, or its strongest performance points + fits your workload, it might be the best hash table implementation. + +[PR45282]: https://github.com/rust-lang/rust/pull/45282 + +# Recent Changes + +See [RELEASES.rst](https://github.com/bluss/indexmap/blob/master/README.rst). diff --git a/README.rst b/README.rst deleted file mode 100644 index 4dac24f4..00000000 --- a/README.rst +++ /dev/null @@ -1,69 +0,0 @@ -indexmap -======== - -|build_status|_ |crates|_ |docs|_ |rustc|_ - -.. |build_status| image:: https://github.com/bluss/indexmap/workflows/Continuous%20integration/badge.svg?branch=master -.. _build_status: https://github.com/bluss/indexmap/actions - -.. |crates| image:: https://img.shields.io/crates/v/indexmap.svg -.. _crates: https://crates.io/crates/indexmap - -.. |docs| image:: https://docs.rs/indexmap/badge.svg -.. _docs: https://docs.rs/indexmap - -.. |rustc| image:: https://img.shields.io/badge/rust-1.56%2B-orange.svg -.. _rustc: https://img.shields.io/badge/rust-1.56%2B-orange.svg - -A pure-Rust hash table which preserves (in a limited sense) insertion order. - -This crate implements compact map and set data-structures, -where the iteration order of the keys is independent from their hash or -value. It preserves insertion order (except after removals), and it -allows lookup of entries by either hash table key or numerical index. - -Note: this crate was originally released under the name ``ordermap``, -but it was renamed to ``indexmap`` to better reflect its features. - -Background -========== - -This was inspired by Python 3.6's new dict implementation (which remembers -the insertion order and is fast to iterate, and is compact in memory). - -Some of those features were translated to Rust, and some were not. The result -was indexmap, a hash table that has following properties: - -- Order is **independent of hash function** and hash values of keys. -- Fast to iterate. -- Indexed in compact space. -- Preserves insertion order **as long** as you don't call ``.remove()``. -- Uses hashbrown for the inner table, just like Rust's libstd ``HashMap`` does. - -Performance ------------ - -``IndexMap`` derives a couple of performance facts directly from how it is constructed, -which is roughly: - - A raw hash table of key-value indices, and a vector of key-value pairs. - -- Iteration is very fast since it is on the dense key-values. -- Removal is fast since it moves memory areas only in the table, - and uses a single swap in the vector. -- Lookup is fast-ish because the initial 7-bit hash lookup uses SIMD, and indices are - densely stored. Lookup also is slow-ish since the actual key-value pairs are stored - separately. (Visible when cpu caches size is limiting.) - -- In practice, ``IndexMap`` has been tested out as the hashmap in rustc in PR45282_ and - the performance was roughly on par across the whole workload. -- If you want the properties of ``IndexMap``, or its strongest performance points - fits your workload, it might be the best hash table implementation. - -.. _PR45282: https://github.com/rust-lang/rust/pull/45282 - - -Recent Changes -============== - -See `RELEASES.rst <./RELEASES.rst>`_. From dec33378bbf8490ca30eefd40dc255d12763479f Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 8 Feb 2022 15:54:37 -0800 Subject: [PATCH 051/236] Convert the RELEASES doc to markdown --- README.md | 2 +- RELEASES.md | 354 +++++++++++++++++++++++++++++++++++++++++++++++++++ RELEASES.rst | 354 --------------------------------------------------- 3 files changed, 355 insertions(+), 355 deletions(-) create mode 100644 RELEASES.md delete mode 100644 RELEASES.rst diff --git a/README.md b/README.md index a227e23d..5dc3f181 100644 --- a/README.md +++ b/README.md @@ -52,4 +52,4 @@ which is roughly: # Recent Changes -See [RELEASES.rst](https://github.com/bluss/indexmap/blob/master/README.rst). +See [RELEASES.md](https://github.com/bluss/indexmap/blob/master/README.md). diff --git a/RELEASES.md b/RELEASES.md new file mode 100644 index 00000000..e217c5a8 --- /dev/null +++ b/RELEASES.md @@ -0,0 +1,354 @@ +- 1.8.1 + + - The new `IndexSet::replace_full` will return the index of the item along + with the replaced value, if any, by @zakcutner in PR [222]. + +[222]: https://github.com/bluss/indexmap/pull/222 + +- 1.8.0 + + - The new `IndexMap::into_keys` and `IndexMap::into_values` will consume + the map into keys or values, respectively, matching Rust 1.54's `HashMap` + methods, by @taiki-e in PR [195]. + + - More of the iterator types implement `Debug`, `ExactSizeIterator`, and + `FusedIterator`, by @cuviper in PR [196]. + + - `IndexMap` and `IndexSet` now implement rayon's `ParallelDrainRange`, + by @cuviper in PR [197]. + + - `IndexMap::with_hasher` and `IndexSet::with_hasher` are now `const` + functions, allowing static maps and sets, by @mwillsey in PR [203]. + + - `IndexMap` and `IndexSet` now implement `From` for arrays, matching + Rust 1.56's implementation for `HashMap`, by @rouge8 in PR [205]. + + - `IndexMap` and `IndexSet` now have methods `sort_unstable_keys`, + `sort_unstable_by`, `sorted_unstable_by`, and `par_*` equivalents, + which sort in-place without preserving the order of equal items, by + @bhgomes in PR [211]. + +[195]: https://github.com/bluss/indexmap/pull/195 +[196]: https://github.com/bluss/indexmap/pull/196 +[197]: https://github.com/bluss/indexmap/pull/197 +[203]: https://github.com/bluss/indexmap/pull/203 +[205]: https://github.com/bluss/indexmap/pull/205 +[211]: https://github.com/bluss/indexmap/pull/211 + +- 1.7.0 + + - **MSRV**: Rust 1.49 or later is now required. + + - The `hashbrown` dependency has been updated to version 0.11. + +- 1.6.2 + + - Fixed to match `std` behavior, `OccupiedEntry::key` now references the + existing key in the map instead of the lookup key, by @cuviper in PR [170]. + + - The new `Entry::or_insert_with_key` matches Rust 1.50's `Entry` method, + passing `&K` to the callback to create a value, by @cuviper in PR [175]. + +[170]: https://github.com/bluss/indexmap/pull/170 +[175]: https://github.com/bluss/indexmap/pull/175 + +- 1.6.1 + + - The new `serde_seq` module implements `IndexMap` serialization as a + sequence to ensure order is preserved, by @cuviper in PR [158]. + + - New methods on maps and sets work like the `Vec`/slice methods by the same name: + `truncate`, `split_off`, `first`, `first_mut`, `last`, `last_mut`, and + `swap_indices`, by @cuviper in PR [160]. + +[158]: https://github.com/bluss/indexmap/pull/158 +[160]: https://github.com/bluss/indexmap/pull/160 + +- 1.6.0 + + - **MSRV**: Rust 1.36 or later is now required. + + - The `hashbrown` dependency has been updated to version 0.9. + +- 1.5.2 + + - The new "std" feature will force the use of `std` for users that explicitly + want the default `S = RandomState`, bypassing the autodetection added in 1.3.0, + by @cuviper in PR [145]. + +[145]: https://github.com/bluss/indexmap/pull/145 + +- 1.5.1 + + - Values can now be indexed by their `usize` position by @cuviper in PR [132]. + + - Some of the generic bounds have been relaxed to match `std` by @cuviper in PR [141]. + + - `drain` now accepts any `R: RangeBounds` by @cuviper in PR [142]. + +[132]: https://github.com/bluss/indexmap/pull/132 +[141]: https://github.com/bluss/indexmap/pull/141 +[142]: https://github.com/bluss/indexmap/pull/142 + +- 1.5.0 + + - **MSRV**: Rust 1.32 or later is now required. + + - The inner hash table is now based on `hashbrown` by @cuviper in PR [131]. + This also completes the method `reserve` and adds `shrink_to_fit`. + + - Add new methods `get_key_value`, `remove_entry`, `swap_remove_entry`, + and `shift_remove_entry`, by @cuviper in PR [136] + + - `Clone::clone_from` reuses allocations by @cuviper in PR [125] + + - Add new method `reverse` by @linclelinkpart5 in PR [128] + +[125]: https://github.com/bluss/indexmap/pull/125 +[128]: https://github.com/bluss/indexmap/pull/128 +[131]: https://github.com/bluss/indexmap/pull/131 +[136]: https://github.com/bluss/indexmap/pull/136 + +- 1.4.0 + + - Add new method `get_index_of` by @Thermatrix in PR [115] and [120] + + - Fix build script rebuild-if-changed configuration to use "build.rs"; + fixes issue [123]. Fix by @cuviper. + + - Dev-dependencies (rand and quickcheck) have been updated. The crate's tests + now run using Rust 1.32 or later (MSRV for building the crate has not changed). + by @kjeremy and @bluss + +[123]: https://github.com/bluss/indexmap/issues/123 +[115]: https://github.com/bluss/indexmap/pull/115 +[120]: https://github.com/bluss/indexmap/pull/120 + +- 1.3.2 + + - Maintenance update to regenerate the published `Cargo.toml`. + +- 1.3.1 + + - Maintenance update for formatting and `autocfg` 1.0. + +- 1.3.0 + + - The deprecation messages in the previous version have been removed. + (The methods have not otherwise changed.) Docs for removal methods have been + improved. + - From Rust 1.36, this crate supports being built **without std**, requiring + `alloc` instead. This is enabled automatically when it is detected that + `std` is not available. There is no crate feature to enable/disable to + trigger this. The new build-dep `autocfg` enables this. + +- 1.2.0 + + - Plain `.remove()` now has a deprecation message, it informs the user + about picking one of the removal functions `swap_remove` and `shift_remove` + which have different performance and order semantics. + Plain `.remove()` will not be removed, the warning message and method + will remain until further. + + - Add new method `shift_remove` for order preserving removal on the map, + and `shift_take` for the corresponding operation on the set. + + - Add methods `swap_remove`, `swap_remove_entry` to `Entry`. + + - Fix indexset/indexmap to support full paths, like `indexmap::indexmap!()` + + - Internal improvements: fix warnings, deprecations and style lints + +- 1.1.0 + + - Added optional feature `"rayon"` that adds parallel iterator support + to `IndexMap` and `IndexSet` using Rayon. This includes all the regular + iterators in parallel versions, and parallel sort. + + - Implemented `Clone` for `map::{Iter, Keys, Values}` and + `set::{Difference, Intersection, Iter, SymmetricDifference, Union}` + + - Implemented `Debug` for `map::{Entry, IntoIter, Iter, Keys, Values}` and + `set::{Difference, Intersection, IntoIter, Iter, SymmetricDifference, Union}` + + - Serde trait `IntoDeserializer` are implemented for `IndexMap` and `IndexSet`. + + - Minimum Rust version requirement increased to Rust 1.30 for development builds. + +- 1.0.2 + + - The new methods `IndexMap::insert_full` and `IndexSet::insert_full` are + both like `insert` with the index included in the return value. + + - The new method `Entry::and_modify` can be used to modify occupied + entries, matching the new methods of `std` maps in Rust 1.26. + + - The new method `Entry::or_default` inserts a default value in unoccupied + entries, matching the new methods of `std` maps in Rust 1.28. + +- 1.0.1 + + - Document Rust version policy for the crate (see rustdoc) + +- 1.0.0 + + - This is the 1.0 release for `indexmap`! (the crate and datastructure + formerly known as “ordermap”) + - `OccupiedEntry::insert` changed its signature, to use `&mut self` for + the method receiver, matching the equivalent method for a standard + `HashMap`. Thanks to @dtolnay for finding this bug. + - The deprecated old names from ordermap were removed: `OrderMap`, + `OrderSet`, `ordermap!{}`, `orderset!{}`. Use the new `IndexMap` + etc names instead. + +- 0.4.1 + + - Renamed crate to `indexmap`; the `ordermap` crate is now deprecated + and the types `OrderMap/Set` now have a deprecation notice. + +- 0.4.0 + + - This is the last release series for this `ordermap` under that name, + because the crate is **going to be renamed** to `indexmap` (with types + `IndexMap`, `IndexSet`) and no change in functionality! + - The map and its associated structs moved into the `map` submodule of the + crate, so that the map and set are symmetric + + + The iterators, `Entry` and other structs are now under `ordermap::map::` + + - Internally refactored `OrderMap` so that all the main algorithms + (insertion, lookup, removal etc) that don't use the `S` parameter (the + hasher) are compiled without depending on `S`, which reduces generics bloat. + + - `Entry` no longer has a type parameter `S`, which is just like + the standard `HashMap`'s entry. + + - Minimum Rust version requirement increased to Rust 1.18 + +- 0.3.5 + + - Documentation improvements + +- 0.3.4 + + - The `.retain()` methods for `OrderMap` and `OrderSet` now + traverse the elements in order, and the retained elements **keep their order** + - Added new methods `.sort_by()`, `.sort_keys()` to `OrderMap` and + `.sort_by()`, `.sort()` to `OrderSet`. These methods allow you to + sort the maps in place efficiently. + +- 0.3.3 + + - Document insertion behaviour better by @lucab + - Updated dependences (no feature changes) by @ignatenkobrain + +- 0.3.2 + + - Add `OrderSet` by @cuviper! + - `OrderMap::drain` is now (too) a double ended iterator. + +- 0.3.1 + + - In all ordermap iterators, forward the `collect` method to the underlying + iterator as well. + - Add crates.io categories. + +- 0.3.0 + + - The methods `get_pair`, `get_pair_index` were both replaced by + `get_full` (and the same for the mutable case). + - Method `swap_remove_pair` replaced by `swap_remove_full`. + - Add trait `MutableKeys` for opt-in mutable key access. Mutable key access + is only possible through the methods of this extension trait. + - Add new trait `Equivalent` for key equivalence. This extends the + `Borrow` trait mechanism for `OrderMap::get` in a backwards compatible + way, just some minor type inference related issues may become apparent. + See [#10] for more information. + - Implement `Extend<(&K, &V)>` by @xfix. + +[#10]: https://github.com/bluss/ordermap/pull/10 + +- 0.2.13 + + - Fix deserialization to support custom hashers by @Techcable. + - Add methods `.index()` on the entry types by @garro95. + +- 0.2.12 + + - Add methods `.with_hasher()`, `.hasher()`. + +- 0.2.11 + + - Support `ExactSizeIterator` for the iterators. By @Binero. + - Use `Box<[Pos]>` internally, saving a word in the `OrderMap` struct. + - Serde support, with crate feature `"serde-1"`. By @xfix. + +- 0.2.10 + + - Add iterator `.drain(..)` by @stevej. + +- 0.2.9 + + - Add method `.is_empty()` by @overvenus. + - Implement `PartialEq, Eq` by @overvenus. + - Add method `.sorted_by()`. + +- 0.2.8 + + - Add iterators `.values()` and `.values_mut()`. + - Fix compatibility with 32-bit platforms. + +- 0.2.7 + + - Add `.retain()`. + +- 0.2.6 + + - Add `OccupiedEntry::remove_entry` and other minor entry methods, + so that it now has all the features of `HashMap`'s entries. + +- 0.2.5 + + - Improved `.pop()` slightly. + +- 0.2.4 + + - Improved performance of `.insert()` ([#3]) by @pczarn. + +[#3]: https://github.com/bluss/ordermap/pull/3 + +- 0.2.3 + + - Generalize `Entry` for now, so that it works on hashmaps with non-default + hasher. However, there's a lingering compat issue since libstd `HashMap` + does not parameterize its entries by the hasher (`S` typarm). + - Special case some iterator methods like `.nth()`. + +- 0.2.2 + + - Disable the verbose `Debug` impl by default. + +- 0.2.1 + + - Fix doc links and clarify docs. + +- 0.2.0 + + - Add more `HashMap` methods & compat with its API. + - Experimental support for `.entry()` (the simplest parts of the API). + - Add `.reserve()` (placeholder impl). + - Add `.remove()` as synonym for `.swap_remove()`. + - Changed `.insert()` to swap value if the entry already exists, and + return `Option`. + - Experimental support as an *indexed* hash map! Added methods + `.get_index()`, `.get_index_mut()`, `.swap_remove_index()`, + `.get_pair_index()`, `.get_pair_index_mut()`. + +- 0.1.2 + + - Implement the 32/32 split idea for `Pos` which improves cache utilization + and lookup performance. + +- 0.1.1 + + - Initial release. diff --git a/RELEASES.rst b/RELEASES.rst deleted file mode 100644 index 402724a2..00000000 --- a/RELEASES.rst +++ /dev/null @@ -1,354 +0,0 @@ -- 1.8.1 - - - The new ``IndexSet::replace_full`` will return the index of the item along - with the replaced value, if any, by @zakcutner in PR 222_. - -.. _222: https://github.com/bluss/indexmap/pull/222 - -- 1.8.0 - - - The new ``IndexMap::into_keys`` and ``IndexMap::into_values`` will consume - the map into keys or values, respectively, matching Rust 1.54's ``HashMap`` - methods, by @taiki-e in PR 195_. - - - More of the iterator types implement ``Debug``, ``ExactSizeIterator``, and - ``FusedIterator``, by @cuviper in PR 196_. - - - ``IndexMap`` and ``IndexSet`` now implement rayon's ``ParallelDrainRange``, - by @cuviper in PR 197_. - - - ``IndexMap::with_hasher`` and ``IndexSet::with_hasher`` are now ``const`` - functions, allowing static maps and sets, by @mwillsey in PR 203_. - - - ``IndexMap`` and ``IndexSet`` now implement ``From`` for arrays, matching - Rust 1.56's implementation for ``HashMap``, by @rouge8 in PR 205_. - - - ``IndexMap`` and ``IndexSet`` now have methods ``sort_unstable_keys``, - ``sort_unstable_by``, ``sorted_unstable_by``, and ``par_*`` equivalents, - which sort in-place without preserving the order of equal items, by - @bhgomes in PR 211_. - -.. _195: https://github.com/bluss/indexmap/pull/195 -.. _196: https://github.com/bluss/indexmap/pull/196 -.. _197: https://github.com/bluss/indexmap/pull/197 -.. _203: https://github.com/bluss/indexmap/pull/203 -.. _205: https://github.com/bluss/indexmap/pull/205 -.. _211: https://github.com/bluss/indexmap/pull/211 - -- 1.7.0 - - - **MSRV**: Rust 1.49 or later is now required. - - - The ``hashbrown`` dependency has been updated to version 0.11. - -- 1.6.2 - - - Fixed to match ``std`` behavior, ``OccupiedEntry::key`` now references the - existing key in the map instead of the lookup key, by @cuviper in PR 170_. - - - The new ``Entry::or_insert_with_key`` matches Rust 1.50's ``Entry`` method, - passing ``&K`` to the callback to create a value, by @cuviper in PR 175_. - -.. _170: https://github.com/bluss/indexmap/pull/170 -.. _175: https://github.com/bluss/indexmap/pull/175 - -- 1.6.1 - - - The new ``serde_seq`` module implements ``IndexMap`` serialization as a - sequence to ensure order is preserved, by @cuviper in PR 158_. - - - New methods on maps and sets work like the ``Vec``/slice methods by the same name: - ``truncate``, ``split_off``, ``first``, ``first_mut``, ``last``, ``last_mut``, and - ``swap_indices``, by @cuviper in PR 160_. - -.. _158: https://github.com/bluss/indexmap/pull/158 -.. _160: https://github.com/bluss/indexmap/pull/160 - -- 1.6.0 - - - **MSRV**: Rust 1.36 or later is now required. - - - The ``hashbrown`` dependency has been updated to version 0.9. - -- 1.5.2 - - - The new "std" feature will force the use of ``std`` for users that explicitly - want the default ``S = RandomState``, bypassing the autodetection added in 1.3.0, - by @cuviper in PR 145_. - -.. _145: https://github.com/bluss/indexmap/pull/145 - -- 1.5.1 - - - Values can now be indexed by their ``usize`` position by @cuviper in PR 132_. - - - Some of the generic bounds have been relaxed to match ``std`` by @cuviper in PR 141_. - - - ``drain`` now accepts any ``R: RangeBounds`` by @cuviper in PR 142_. - -.. _132: https://github.com/bluss/indexmap/pull/132 -.. _141: https://github.com/bluss/indexmap/pull/141 -.. _142: https://github.com/bluss/indexmap/pull/142 - -- 1.5.0 - - - **MSRV**: Rust 1.32 or later is now required. - - - The inner hash table is now based on ``hashbrown`` by @cuviper in PR 131_. - This also completes the method ``reserve`` and adds ``shrink_to_fit``. - - - Add new methods ``get_key_value``, ``remove_entry``, ``swap_remove_entry``, - and ``shift_remove_entry``, by @cuviper in PR 136_ - - - ``Clone::clone_from`` reuses allocations by @cuviper in PR 125_ - - - Add new method ``reverse`` by @linclelinkpart5 in PR 128_ - -.. _125: https://github.com/bluss/indexmap/pull/125 -.. _128: https://github.com/bluss/indexmap/pull/128 -.. _131: https://github.com/bluss/indexmap/pull/131 -.. _136: https://github.com/bluss/indexmap/pull/136 - -- 1.4.0 - - - Add new method ``get_index_of`` by @Thermatrix in PR 115_ and 120_ - - - Fix build script rebuild-if-changed configuration to use "build.rs"; - fixes issue 123_. Fix by @cuviper. - - - Dev-dependencies (rand and quickcheck) have been updated. The crate's tests - now run using Rust 1.32 or later (MSRV for building the crate has not changed). - by @kjeremy and @bluss - -.. _123: https://github.com/bluss/indexmap/issues/123 -.. _115: https://github.com/bluss/indexmap/pull/115 -.. _120: https://github.com/bluss/indexmap/pull/120 - -- 1.3.2 - - - Maintenance update to regenerate the published `Cargo.toml`. - -- 1.3.1 - - - Maintenance update for formatting and ``autocfg`` 1.0. - -- 1.3.0 - - - The deprecation messages in the previous version have been removed. - (The methods have not otherwise changed.) Docs for removal methods have been - improved. - - From Rust 1.36, this crate supports being built **without std**, requiring - ``alloc`` instead. This is enabled automatically when it is detected that - ``std`` is not available. There is no crate feature to enable/disable to - trigger this. The new build-dep ``autocfg`` enables this. - -- 1.2.0 - - - Plain ``.remove()`` now has a deprecation message, it informs the user - about picking one of the removal functions ``swap_remove`` and ``shift_remove`` - which have different performance and order semantics. - Plain ``.remove()`` will not be removed, the warning message and method - will remain until further. - - - Add new method ``shift_remove`` for order preserving removal on the map, - and ``shift_take`` for the corresponding operation on the set. - - - Add methods ``swap_remove``, ``swap_remove_entry`` to ``Entry``. - - - Fix indexset/indexmap to support full paths, like ``indexmap::indexmap!()`` - - - Internal improvements: fix warnings, deprecations and style lints - -- 1.1.0 - - - Added optional feature `"rayon"` that adds parallel iterator support - to `IndexMap` and `IndexSet` using Rayon. This includes all the regular - iterators in parallel versions, and parallel sort. - - - Implemented ``Clone`` for ``map::{Iter, Keys, Values}`` and - ``set::{Difference, Intersection, Iter, SymmetricDifference, Union}`` - - - Implemented ``Debug`` for ``map::{Entry, IntoIter, Iter, Keys, Values}`` and - ``set::{Difference, Intersection, IntoIter, Iter, SymmetricDifference, Union}`` - - - Serde trait ``IntoDeserializer`` are implemented for ``IndexMap`` and ``IndexSet``. - - - Minimum Rust version requirement increased to Rust 1.30 for development builds. - -- 1.0.2 - - - The new methods ``IndexMap::insert_full`` and ``IndexSet::insert_full`` are - both like ``insert`` with the index included in the return value. - - - The new method ``Entry::and_modify`` can be used to modify occupied - entries, matching the new methods of ``std`` maps in Rust 1.26. - - - The new method ``Entry::or_default`` inserts a default value in unoccupied - entries, matching the new methods of ``std`` maps in Rust 1.28. - -- 1.0.1 - - - Document Rust version policy for the crate (see rustdoc) - -- 1.0.0 - - - This is the 1.0 release for ``indexmap``! (the crate and datastructure - formerly known as “ordermap”) - - ``OccupiedEntry::insert`` changed its signature, to use ``&mut self`` for - the method receiver, matching the equivalent method for a standard - ``HashMap``. Thanks to @dtolnay for finding this bug. - - The deprecated old names from ordermap were removed: ``OrderMap``, - ``OrderSet``, ``ordermap!{}``, ``orderset!{}``. Use the new ``IndexMap`` - etc names instead. - -- 0.4.1 - - - Renamed crate to ``indexmap``; the ``ordermap`` crate is now deprecated - and the types ``OrderMap/Set`` now have a deprecation notice. - -- 0.4.0 - - - This is the last release series for this ``ordermap`` under that name, - because the crate is **going to be renamed** to ``indexmap`` (with types - ``IndexMap``, ``IndexSet``) and no change in functionality! - - The map and its associated structs moved into the ``map`` submodule of the - crate, so that the map and set are symmetric - - + The iterators, ``Entry`` and other structs are now under ``ordermap::map::`` - - - Internally refactored ``OrderMap`` so that all the main algorithms - (insertion, lookup, removal etc) that don't use the ``S`` parameter (the - hasher) are compiled without depending on ``S``, which reduces generics bloat. - - - ``Entry`` no longer has a type parameter ``S``, which is just like - the standard ``HashMap``'s entry. - - - Minimum Rust version requirement increased to Rust 1.18 - -- 0.3.5 - - - Documentation improvements - -- 0.3.4 - - - The ``.retain()`` methods for ``OrderMap`` and ``OrderSet`` now - traverse the elements in order, and the retained elements **keep their order** - - Added new methods ``.sort_by()``, ``.sort_keys()`` to ``OrderMap`` and - ``.sort_by()``, ``.sort()`` to ``OrderSet``. These methods allow you to - sort the maps in place efficiently. - -- 0.3.3 - - - Document insertion behaviour better by @lucab - - Updated dependences (no feature changes) by @ignatenkobrain - -- 0.3.2 - - - Add ``OrderSet`` by @cuviper! - - ``OrderMap::drain`` is now (too) a double ended iterator. - -- 0.3.1 - - - In all ordermap iterators, forward the ``collect`` method to the underlying - iterator as well. - - Add crates.io categories. - -- 0.3.0 - - - The methods ``get_pair``, ``get_pair_index`` were both replaced by - ``get_full`` (and the same for the mutable case). - - Method ``swap_remove_pair`` replaced by ``swap_remove_full``. - - Add trait ``MutableKeys`` for opt-in mutable key access. Mutable key access - is only possible through the methods of this extension trait. - - Add new trait ``Equivalent`` for key equivalence. This extends the - ``Borrow`` trait mechanism for ``OrderMap::get`` in a backwards compatible - way, just some minor type inference related issues may become apparent. - See `#10`__ for more information. - - Implement ``Extend<(&K, &V)>`` by @xfix. - -__ https://github.com/bluss/ordermap/pull/10 - -- 0.2.13 - - - Fix deserialization to support custom hashers by @Techcable. - - Add methods ``.index()`` on the entry types by @garro95. - -- 0.2.12 - - - Add methods ``.with_hasher()``, ``.hasher()``. - -- 0.2.11 - - - Support ``ExactSizeIterator`` for the iterators. By @Binero. - - Use ``Box<[Pos]>`` internally, saving a word in the ``OrderMap`` struct. - - Serde support, with crate feature ``"serde-1"``. By @xfix. - -- 0.2.10 - - - Add iterator ``.drain(..)`` by @stevej. - -- 0.2.9 - - - Add method ``.is_empty()`` by @overvenus. - - Implement ``PartialEq, Eq`` by @overvenus. - - Add method ``.sorted_by()``. - -- 0.2.8 - - - Add iterators ``.values()`` and ``.values_mut()``. - - Fix compatibility with 32-bit platforms. - -- 0.2.7 - - - Add ``.retain()``. - -- 0.2.6 - - - Add ``OccupiedEntry::remove_entry`` and other minor entry methods, - so that it now has all the features of ``HashMap``'s entries. - -- 0.2.5 - - - Improved ``.pop()`` slightly. - -- 0.2.4 - - - Improved performance of ``.insert()`` (`#3`__) by @pczarn. - -__ https://github.com/bluss/ordermap/pull/3 - -- 0.2.3 - - - Generalize ``Entry`` for now, so that it works on hashmaps with non-default - hasher. However, there's a lingering compat issue since libstd ``HashMap`` - does not parameterize its entries by the hasher (``S`` typarm). - - Special case some iterator methods like ``.nth()``. - -- 0.2.2 - - - Disable the verbose ``Debug`` impl by default. - -- 0.2.1 - - - Fix doc links and clarify docs. - -- 0.2.0 - - - Add more ``HashMap`` methods & compat with its API. - - Experimental support for ``.entry()`` (the simplest parts of the API). - - Add ``.reserve()`` (placeholder impl). - - Add ``.remove()`` as synonym for ``.swap_remove()``. - - Changed ``.insert()`` to swap value if the entry already exists, and - return ``Option``. - - Experimental support as an *indexed* hash map! Added methods - ``.get_index()``, ``.get_index_mut()``, ``.swap_remove_index()``, - ``.get_pair_index()``, ``.get_pair_index_mut()``. - -- 0.1.2 - - - Implement the 32/32 split idea for ``Pos`` which improves cache utilization - and lookup performance. - -- 0.1.1 - - - Initial release. From b41fc6b5ddd3a265d5b3daac947209cbbc0d025b Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 8 Feb 2022 16:14:11 -0800 Subject: [PATCH 052/236] Add preliminary release notes for 2.0.0 --- RELEASES.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index e217c5a8..d1051fc9 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,20 @@ +- 2.0.0 (pending) + + - **MSRV**: Rust 1.56 or later is now required. + + - The `hashbrown` dependency has been updated to version 0.12. + + - The `"std"` feature is no longer auto-detected. It is included in the + default feature set, or else can be enabled like any other Cargo feature. + + - The `"serde-1"` feature has been removed, leaving just the optional + `"serde"` dependency to be enabled like a feature itself. + + - `IndexMap::get_index_mut` now returns `Option<(&K, &mut V)>`, changing + the key part from `&mut K` to `&K`. There is also a new alternative + `MutableKeys::get_index_mut2` to access the former behavior. + + - 1.8.1 - The new `IndexSet::replace_full` will return the index of the item along From 4ddce3a976c1bc88a6373391da542729c4bb1815 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 8 Feb 2022 17:14:59 -0800 Subject: [PATCH 053/236] impl Debug for IterMut and ValuesMut --- RELEASES.md | 2 ++ src/map.rs | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index d1051fc9..3bef4859 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -13,6 +13,8 @@ - `IndexMap::get_index_mut` now returns `Option<(&K, &mut V)>`, changing the key part from `&mut K` to `&K`. There is also a new alternative `MutableKeys::get_index_mut2` to access the former behavior. + + - `IterMut` and `ValuesMut` now implement `Debug`. - 1.8.1 diff --git a/src/map.rs b/src/map.rs index 4c6a0229..36291658 100644 --- a/src/map.rs +++ b/src/map.rs @@ -984,7 +984,12 @@ impl ExactSizeIterator for ValuesMut<'_, K, V> { impl FusedIterator for ValuesMut<'_, K, V> {} -// TODO: `impl Debug for ValuesMut` once we have MSRV 1.53 for `slice::IterMut::as_slice` +impl fmt::Debug for ValuesMut<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let iter = self.iter.as_slice().iter().map(Bucket::value_ref); + f.debug_list().entries(iter).finish() + } +} /// An owning iterator over the values of a `IndexMap`. /// @@ -1095,7 +1100,12 @@ impl ExactSizeIterator for IterMut<'_, K, V> { impl FusedIterator for IterMut<'_, K, V> {} -// TODO: `impl Debug for IterMut` once we have MSRV 1.53 for `slice::IterMut::as_slice` +impl fmt::Debug for IterMut<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let iter = self.iter.as_slice().iter().map(Bucket::refs); + f.debug_list().entries(iter).finish() + } +} /// An owning iterator over the entries of a `IndexMap`. /// From 42fab9923135e333fd91ca7f0678dc30be1f705f Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 8 Feb 2022 17:27:41 -0800 Subject: [PATCH 054/236] Use a more standard way to seal MutableKeys Ref: https://rust-lang.github.io/api-guidelines/future-proofing.html --- src/mutable_keys.rs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/mutable_keys.rs b/src/mutable_keys.rs index fad7d60f..7efc779b 100644 --- a/src/mutable_keys.rs +++ b/src/mutable_keys.rs @@ -2,8 +2,6 @@ use core::hash::{BuildHasher, Hash}; use super::{Bucket, Entries, Equivalent, IndexMap}; -pub struct PrivateMarker {} - /// Opt-in mutable access to keys. /// /// These methods expose `&mut K`, mutable references to the key as it is stored @@ -16,7 +14,9 @@ pub struct PrivateMarker {} /// implementing PartialEq, Eq, or Hash incorrectly would be). /// /// `use` this trait to enable its methods for `IndexMap`. -pub trait MutableKeys { +/// +/// This trait is sealed and cannot be implemented for types outside this crate. +pub trait MutableKeys: private::Sealed { type Key; type Value; @@ -47,12 +47,6 @@ pub trait MutableKeys { fn retain2(&mut self, keep: F) where F: FnMut(&mut Self::Key, &mut Self::Value) -> bool; - - #[doc(hidden)] - /// This method is not useful in itself – it is there to “seal” the trait - /// for external implementation, so that we can add methods without - /// causing breaking changes. - fn __private_marker(&self) -> PrivateMarker; } /// Opt-in mutable access to keys. @@ -88,8 +82,10 @@ where { self.retain_mut(keep) } +} - fn __private_marker(&self) -> PrivateMarker { - PrivateMarker {} - } +mod private { + pub trait Sealed {} + + impl Sealed for super::IndexMap {} } From 94d11970208e8937e9bd9187b4863706a81e19df Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 8 Feb 2022 17:42:03 -0800 Subject: [PATCH 055/236] Add shrink_to --- RELEASES.md | 4 +++- src/map.rs | 9 ++++++++- src/map/core.rs | 8 ++++---- src/set.rs | 7 +++++++ 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 3bef4859..f27a0bc8 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -15,7 +15,9 @@ `MutableKeys::get_index_mut2` to access the former behavior. - `IterMut` and `ValuesMut` now implement `Debug`. - + + - The new `IndexMap::shrink_to` and `IndexSet::shrink_to` methods shrink + the capacity with a lower bound. - 1.8.1 diff --git a/src/map.rs b/src/map.rs index 36291658..604538df 100644 --- a/src/map.rs +++ b/src/map.rs @@ -332,7 +332,14 @@ where /// /// Computes in **O(n)** time. pub fn shrink_to_fit(&mut self) { - self.core.shrink_to_fit(); + self.core.shrink_to(0); + } + + /// Shrink the capacity of the map with a lower limit. + /// + /// Computes in **O(n)** time. + pub fn shrink_to(&mut self, min_capacity: usize) { + self.core.shrink_to(min_capacity); } fn hash(&self, key: &Q) -> HashValue { diff --git a/src/map/core.rs b/src/map/core.rs index 0ed9b1ff..6479135d 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -201,10 +201,10 @@ impl IndexMapCore { self.entries.reserve_exact(additional); } - /// Shrink the capacity of the map as much as possible. - pub(crate) fn shrink_to_fit(&mut self) { - self.indices.shrink_to(0, get_hash(&self.entries)); - self.entries.shrink_to_fit(); + /// Shrink the capacity of the map with a lower bound + pub(crate) fn shrink_to(&mut self, min_capacity: usize) { + self.indices.shrink_to(min_capacity, get_hash(&self.entries)); + self.entries.shrink_to(min_capacity); } /// Remove the last key-value pair diff --git a/src/set.rs b/src/set.rs index 387168d4..fa157b0f 100644 --- a/src/set.rs +++ b/src/set.rs @@ -268,6 +268,13 @@ where self.map.shrink_to_fit(); } + /// Shrink the capacity of the set with a lower limit. + /// + /// Computes in **O(n)** time. + pub fn shrink_to(&mut self, min_capacity: usize) { + self.map.shrink_to(min_capacity); + } + /// Insert the value into the set. /// /// If an equivalent item already exists in the set, it returns From 18b8a6f1495cc075662eb36595ad46d5ba5d896a Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 9 Feb 2022 10:39:52 -0800 Subject: [PATCH 056/236] no_std has to opt-out of default features --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f04cce5a..fddcca0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,7 +72,7 @@ jobs: target: ${{ matrix.target }} - name: Tests run: | - cargo build -vv --target=${{ matrix.target }} + cargo build -vv --target=${{ matrix.target }} --no-default-features cargo build -v -p test-nostd --target=${{ matrix.target }} clippy: From c71567597858e1b186479ad67247fe2d810eaabe Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 29 Mar 2022 16:16:29 -0700 Subject: [PATCH 057/236] Fix clippy::needless_borrow --- src/rayon/set.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rayon/set.rs b/src/rayon/set.rs index 5a4ac97d..6749dc0d 100644 --- a/src/rayon/set.rs +++ b/src/rayon/set.rs @@ -295,7 +295,7 @@ where { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_list() - .entries(self.set1.difference(&self.set2)) + .entries(self.set1.difference(self.set2)) .finish() } } @@ -346,7 +346,7 @@ where { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_list() - .entries(self.set1.intersection(&self.set2)) + .entries(self.set1.intersection(self.set2)) .finish() } } @@ -397,7 +397,7 @@ where { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_list() - .entries(self.set1.symmetric_difference(&self.set2)) + .entries(self.set1.symmetric_difference(self.set2)) .finish() } } @@ -447,7 +447,7 @@ where S2: BuildHasher, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.set1.union(&self.set2)).finish() + f.debug_list().entries(self.set1.union(self.set2)).finish() } } From 4fba5922b48d1ad0329274bd89d52fd709563144 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sat, 27 Feb 2021 21:59:53 -0800 Subject: [PATCH 058/236] Add dynamically-sized slices of maps and sets --- src/map.rs | 2 + src/map/slice.rs | 319 +++++++++++++++++++++++++++++++++++++++++++++++ src/set.rs | 10 +- src/set/slice.rs | 193 ++++++++++++++++++++++++++++ 4 files changed, 521 insertions(+), 3 deletions(-) create mode 100644 src/map/slice.rs create mode 100644 src/set/slice.rs diff --git a/src/map.rs b/src/map.rs index 604538df..38cf326b 100644 --- a/src/map.rs +++ b/src/map.rs @@ -2,7 +2,9 @@ //! pairs is independent of the hash values of the keys. mod core; +mod slice; +pub use self::slice::Slice; pub use crate::mutable_keys::MutableKeys; #[cfg(feature = "rayon")] diff --git a/src/map/slice.rs b/src/map/slice.rs new file mode 100644 index 00000000..4ccfb287 --- /dev/null +++ b/src/map/slice.rs @@ -0,0 +1,319 @@ +use super::{Bucket, Entries, IndexMap, Iter, IterMut, Keys, Values, ValuesMut}; + +use core::cmp::Ordering; +use core::fmt; +use core::hash::{Hash, Hasher}; +use core::ops::{self, Index, IndexMut}; + +/// A dynamically-sized slice of key-value pairs in an `IndexMap`. +/// +/// This supports indexed operations much like a `[(K, V)]` slice, +/// but not any hashed operations on the map keys. +/// +/// Unlike `IndexMap`, `Slice` does consider the order for `PartialEq` +/// and `Eq`, and it also implements `PartialOrd`, `Ord`, and `Hash`. +#[repr(transparent)] +pub struct Slice { + entries: [Bucket], +} + +#[allow(unsafe_code)] +impl Slice { + fn from_slice(entries: &[Bucket]) -> &Self { + // SAFETY: `Slice` is a transparent wrapper around `[Bucket]`, + // and the lifetimes are bound together by this function's signature. + unsafe { &*(entries as *const [Bucket] as *const Self) } + } + + fn from_mut_slice(entries: &mut [Bucket]) -> &mut Self { + // SAFETY: `Slice` is a transparent wrapper around `[Bucket]`, + // and the lifetimes are bound together by this function's signature. + unsafe { &mut *(entries as *mut [Bucket] as *mut Self) } + } +} + +impl IndexMap { + /// Returns a slice of all the entries in the map. + pub fn as_slice(&self) -> &Slice { + Slice::from_slice(self.as_entries()) + } + + /// Returns a mutable slice of all the entries in the map. + pub fn as_mut_slice(&mut self) -> &mut Slice { + Slice::from_mut_slice(self.as_entries_mut()) + } +} + +impl<'a, K, V> Iter<'a, K, V> { + /// Returns a slice of the remaining entries in the iterator. + pub fn as_slice(&self) -> &'a Slice { + Slice::from_slice(self.iter.as_slice()) + } +} + +impl<'a, K, V> IterMut<'a, K, V> { + /// Returns a slice of the remaining entries in the iterator. + /// + /// To avoid creating `&mut` references that alias, this is forced to consume the iterator. + pub fn into_slice(self) -> &'a mut Slice { + Slice::from_mut_slice(self.iter.into_slice()) + } +} + +impl Slice { + /// Return the number of key-value pairs in the map slice. + #[inline] + pub fn len(&self) -> usize { + self.entries.len() + } + + /// Returns true if the map slice contains no elements. + #[inline] + pub fn is_empty(&self) -> bool { + self.entries.is_empty() + } + + /// Get a key-value pair by index. + /// + /// Valid indices are *0 <= index < self.len()* + pub fn get_index(&self, index: usize) -> Option<(&K, &V)> { + self.entries.get(index).map(Bucket::refs) + } + + /// Get a key-value pair by index, with mutable access to the value. + /// + /// Valid indices are *0 <= index < self.len()* + pub fn get_index_mut(&mut self, index: usize) -> Option<(&K, &mut V)> { + // NB: we're not returning `&mut K` like `IndexMap::get_index_mut`, + // because that was a mistake that should have required `MutableKeys`. + self.entries.get_mut(index).map(Bucket::ref_mut) + } + + /// Get the first key-value pair. + pub fn first(&self) -> Option<(&K, &V)> { + self.entries.first().map(Bucket::refs) + } + + /// Get the first key-value pair, with mutable access to the value. + pub fn first_mut(&mut self) -> Option<(&K, &mut V)> { + self.entries.first_mut().map(Bucket::ref_mut) + } + + /// Get the last key-value pair. + pub fn last(&self) -> Option<(&K, &V)> { + self.entries.last().map(Bucket::refs) + } + + /// Get the last key-value pair, with mutable access to the value. + pub fn last_mut(&mut self) -> Option<(&K, &mut V)> { + self.entries.last_mut().map(Bucket::ref_mut) + } + + /// Divides one slice into two at an index. + /// + /// ***Panics*** if `index > len`. + pub fn split_at(&self, index: usize) -> (&Self, &Self) { + let (first, second) = self.entries.split_at(index); + (Self::from_slice(first), Self::from_slice(second)) + } + + /// Divides one mutable slice into two at an index. + /// + /// ***Panics*** if `index > len`. + pub fn split_at_mut(&mut self, index: usize) -> (&mut Self, &mut Self) { + let (first, second) = self.entries.split_at_mut(index); + (Self::from_mut_slice(first), Self::from_mut_slice(second)) + } + + /// Returns the first key-value pair and the rest of the slice, + /// or `None` if it is empty. + pub fn split_first(&self) -> Option<((&K, &V), &Self)> { + if let Some((first, rest)) = self.entries.split_first() { + Some((first.refs(), Self::from_slice(rest))) + } else { + None + } + } + + /// Returns the first key-value pair and the rest of the slice, + /// with mutable access to the value, or `None` if it is empty. + pub fn split_first_mut(&mut self) -> Option<((&K, &mut V), &mut Self)> { + if let Some((first, rest)) = self.entries.split_first_mut() { + Some((first.ref_mut(), Self::from_mut_slice(rest))) + } else { + None + } + } + + /// Returns the last key-value pair and the rest of the slice, + /// or `None` if it is empty. + pub fn split_last(&self) -> Option<((&K, &V), &Self)> { + if let Some((last, rest)) = self.entries.split_last() { + Some((last.refs(), Self::from_slice(rest))) + } else { + None + } + } + + /// Returns the last key-value pair and the rest of the slice, + /// with mutable access to the value, or `None` if it is empty. + pub fn split_last_mut(&mut self) -> Option<((&K, &mut V), &mut Self)> { + if let Some((last, rest)) = self.entries.split_last_mut() { + Some((last.ref_mut(), Self::from_mut_slice(rest))) + } else { + None + } + } + + /// Return an iterator over the key-value pairs of the map slice. + pub fn iter(&self) -> Iter<'_, K, V> { + Iter { + iter: self.entries.iter(), + } + } + + /// Return an iterator over the key-value pairs of the map slice. + pub fn iter_mut(&mut self) -> IterMut<'_, K, V> { + IterMut { + iter: self.entries.iter_mut(), + } + } + + /// Return an iterator over the keys of the map slice. + pub fn keys(&self) -> Keys<'_, K, V> { + Keys { + iter: self.entries.iter(), + } + } + + /// Return an iterator over the values of the map slice. + pub fn values(&self) -> Values<'_, K, V> { + Values { + iter: self.entries.iter(), + } + } + + /// Return an iterator over mutable references to the the values of the map slice. + pub fn values_mut(&mut self) -> ValuesMut<'_, K, V> { + ValuesMut { + iter: self.entries.iter_mut(), + } + } +} + +impl<'a, K, V> IntoIterator for &'a Slice { + type IntoIter = Iter<'a, K, V>; + type Item = (&'a K, &'a V); + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, K, V> IntoIterator for &'a mut Slice { + type IntoIter = IterMut<'a, K, V>; + type Item = (&'a K, &'a mut V); + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +impl Default for &'_ Slice { + fn default() -> Self { + Slice::from_slice(&[]) + } +} + +impl fmt::Debug for Slice { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self).finish() + } +} + +impl PartialEq for Slice { + fn eq(&self, other: &Self) -> bool { + self.len() == other.len() && self.iter().eq(other) + } +} + +impl Eq for Slice {} + +impl PartialOrd for Slice { + fn partial_cmp(&self, other: &Self) -> Option { + self.iter().partial_cmp(other) + } +} + +impl Ord for Slice { + fn cmp(&self, other: &Self) -> Ordering { + self.iter().cmp(other) + } +} + +impl Hash for Slice { + fn hash(&self, state: &mut H) { + self.len().hash(state); + for (key, value) in self { + key.hash(state); + value.hash(state); + } + } +} + +impl Index for Slice { + type Output = V; + + fn index(&self, index: usize) -> &V { + &self.entries[index].value + } +} + +impl IndexMut for Slice { + fn index_mut(&mut self, index: usize) -> &mut V { + &mut self.entries[index].value + } +} + +// We can't have `impl> Index` because that conflicts +// both upstream with `Index` and downstream with `Index<&Q>`. +// Instead, we repeat the implementations for all the core range types. +macro_rules! impl_index { + ($($range:ty),*) => {$( + impl Index<$range> for IndexMap { + type Output = Slice; + + fn index(&self, range: $range) -> &Self::Output { + Slice::from_slice(&self.as_entries()[range]) + } + } + + impl IndexMut<$range> for IndexMap { + fn index_mut(&mut self, range: $range) -> &mut Self::Output { + Slice::from_mut_slice(&mut self.as_entries_mut()[range]) + } + } + + impl Index<$range> for Slice { + type Output = Slice; + + fn index(&self, range: $range) -> &Self { + Self::from_slice(&self.entries[range]) + } + } + + impl IndexMut<$range> for Slice { + fn index_mut(&mut self, range: $range) -> &mut Self { + Self::from_mut_slice(&mut self.entries[range]) + } + } + )*} +} +impl_index!( + ops::Range, + ops::RangeFrom, + ops::RangeFull, + ops::RangeInclusive, + ops::RangeTo, + ops::RangeToInclusive +); diff --git a/src/set.rs b/src/set.rs index fa157b0f..4484693a 100644 --- a/src/set.rs +++ b/src/set.rs @@ -1,5 +1,9 @@ //! A hash set implemented using `IndexMap` +mod slice; + +pub use self::slice::Slice; + #[cfg(feature = "rayon")] pub use crate::rayon::set as rayon; @@ -12,7 +16,7 @@ use core::fmt; use core::hash::{BuildHasher, Hash}; use core::iter::{Chain, FusedIterator}; use core::ops::{BitAnd, BitOr, BitXor, Index, RangeBounds, Sub}; -use core::slice; +use core::slice::Iter as SliceIter; use super::{Entries, Equivalent, IndexMap}; @@ -192,7 +196,7 @@ impl IndexSet { /// Return an iterator over the values of the set, in their order pub fn iter(&self) -> Iter<'_, T> { Iter { - iter: self.map.keys().iter, + iter: self.map.as_entries().iter(), } } @@ -791,7 +795,7 @@ impl fmt::Debug for IntoIter { /// [`IndexSet`]: struct.IndexSet.html /// [`iter`]: struct.IndexSet.html#method.iter pub struct Iter<'a, T> { - iter: slice::Iter<'a, Bucket>, + iter: SliceIter<'a, Bucket>, } impl<'a, T> Iterator for Iter<'a, T> { diff --git a/src/set/slice.rs b/src/set/slice.rs new file mode 100644 index 00000000..8962799f --- /dev/null +++ b/src/set/slice.rs @@ -0,0 +1,193 @@ +use super::{Bucket, Entries, IndexSet, Iter}; + +use core::cmp::Ordering; +use core::fmt; +use core::hash::{Hash, Hasher}; +use core::ops::{self, Index}; + +/// A dynamically-sized slice of values in an `IndexSet`. +/// +/// This supports indexed operations much like a `[T]` slice, +/// but not any hashed operations on the values. +/// +/// Unlike `IndexSet`, `Slice` does consider the order for `PartialEq` +/// and `Eq`, and it also implements `PartialOrd`, `Ord`, and `Hash`. +#[repr(transparent)] +pub struct Slice { + entries: [Bucket], +} + +#[allow(unsafe_code)] +impl Slice { + fn from_slice(entries: &[Bucket]) -> &Self { + // SAFETY: `Slice` is a transparent wrapper around `[Bucket]`, + // and the lifetimes are bound together by this function's signature. + unsafe { &*(entries as *const [Bucket] as *const Self) } + } +} + +impl IndexSet { + /// Returns a slice of all the values in the set. + pub fn as_slice(&self) -> &Slice { + Slice::from_slice(self.as_entries()) + } +} + +impl<'a, T> Iter<'a, T> { + /// Returns a slice of the remaining entries in the iterator. + pub fn as_slice(&self) -> &'a Slice { + Slice::from_slice(self.iter.as_slice()) + } +} + +impl Slice { + /// Return the number of elements in the set slice. + pub fn len(&self) -> usize { + self.entries.len() + } + + /// Returns true if the set slice contains no elements. + pub fn is_empty(&self) -> bool { + self.entries.is_empty() + } + + /// Get a value by index. + /// + /// Valid indices are *0 <= index < self.len()* + pub fn get_index(&self, index: usize) -> Option<&T> { + self.entries.get(index).map(Bucket::key_ref) + } + + /// Get the first value. + pub fn first(&self) -> Option<&T> { + self.entries.first().map(Bucket::key_ref) + } + + /// Get the last value. + pub fn last(&self) -> Option<&T> { + self.entries.last().map(Bucket::key_ref) + } + + /// Divides one slice into two at an index. + /// + /// ***Panics*** if `index > len`. + pub fn split_at(&self, index: usize) -> (&Self, &Self) { + let (first, second) = self.entries.split_at(index); + (Self::from_slice(first), Self::from_slice(second)) + } + + /// Returns the first value and the rest of the slice, + /// or `None` if it is empty. + pub fn split_first(&self) -> Option<(&T, &Self)> { + if let Some((first, rest)) = self.entries.split_first() { + Some((&first.key, Self::from_slice(rest))) + } else { + None + } + } + + /// Returns the last value and the rest of the slice, + /// or `None` if it is empty. + pub fn split_last(&self) -> Option<(&T, &Self)> { + if let Some((last, rest)) = self.entries.split_last() { + Some((&last.key, Self::from_slice(rest))) + } else { + None + } + } + + /// Return an iterator over the values of the set slice. + pub fn iter(&self) -> Iter<'_, T> { + Iter { + iter: self.entries.iter(), + } + } +} + +impl<'a, T> IntoIterator for &'a Slice { + type IntoIter = Iter<'a, T>; + type Item = &'a T; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl Default for &'_ Slice { + fn default() -> Self { + Slice::from_slice(&[]) + } +} + +impl fmt::Debug for Slice { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self).finish() + } +} + +impl PartialEq for Slice { + fn eq(&self, other: &Self) -> bool { + self.len() == other.len() && self.iter().eq(other) + } +} + +impl Eq for Slice {} + +impl PartialOrd for Slice { + fn partial_cmp(&self, other: &Self) -> Option { + self.iter().partial_cmp(other) + } +} + +impl Ord for Slice { + fn cmp(&self, other: &Self) -> Ordering { + self.iter().cmp(other) + } +} + +impl Hash for Slice { + fn hash(&self, state: &mut H) { + self.len().hash(state); + for value in self { + value.hash(state); + } + } +} + +impl Index for Slice { + type Output = T; + + fn index(&self, index: usize) -> &Self::Output { + &self.entries[index].key + } +} + +// We can't have `impl> Index` because that conflicts with `Index`. +// Instead, we repeat the implementations for all the core range types. +macro_rules! impl_index { + ($($range:ty),*) => {$( + impl Index<$range> for IndexSet { + type Output = Slice; + + fn index(&self, range: $range) -> &Self::Output { + Slice::from_slice(&self.as_entries()[range]) + } + } + + impl Index<$range> for Slice { + type Output = Self; + + fn index(&self, range: $range) -> &Self::Output { + Slice::from_slice(&self.entries[range]) + } + } + )*} +} +impl_index!( + ops::Range, + ops::RangeFrom, + ops::RangeFull, + ops::RangeInclusive, + ops::RangeTo, + ops::RangeToInclusive +); From e2c6a2f9869080224bdfc51f40554b0b98659424 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sat, 27 Feb 2021 22:59:42 -0800 Subject: [PATCH 059/236] Create parallel iterators from slices --- src/map/slice.rs | 2 +- src/rayon/map.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++++++ src/rayon/set.rs | 16 ++++++++++ src/set/slice.rs | 2 +- 4 files changed, 99 insertions(+), 2 deletions(-) diff --git a/src/map/slice.rs b/src/map/slice.rs index 4ccfb287..741b06de 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -14,7 +14,7 @@ use core::ops::{self, Index, IndexMut}; /// and `Eq`, and it also implements `PartialOrd`, `Ord`, and `Hash`. #[repr(transparent)] pub struct Slice { - entries: [Bucket], + pub(crate) entries: [Bucket], } #[allow(unsafe_code)] diff --git a/src/rayon/map.rs b/src/rayon/map.rs index 8819f13e..021b63ba 100644 --- a/src/rayon/map.rs +++ b/src/rayon/map.rs @@ -15,6 +15,7 @@ use core::fmt; use core::hash::{BuildHasher, Hash}; use core::ops::RangeBounds; +use crate::map::Slice; use crate::Bucket; use crate::Entries; use crate::IndexMap; @@ -79,6 +80,22 @@ where } } +/// Requires crate feature `"rayon"`. +impl<'a, K, V> IntoParallelIterator for &'a Slice +where + K: Sync, + V: Sync, +{ + type Item = (&'a K, &'a V); + type Iter = ParIter<'a, K, V>; + + fn into_par_iter(self) -> Self::Iter { + ParIter { + entries: &self.entries, + } + } +} + /// A parallel iterator over the entries of a `IndexMap`. /// /// This `struct` is created by the [`par_iter`] method on [`IndexMap`] @@ -129,6 +146,22 @@ where } } +/// Requires crate feature `"rayon"`. +impl<'a, K, V> IntoParallelIterator for &'a mut Slice +where + K: Sync + Send, + V: Send, +{ + type Item = (&'a K, &'a mut V); + type Iter = ParIterMut<'a, K, V>; + + fn into_par_iter(self) -> Self::Iter { + ParIterMut { + entries: &mut self.entries, + } + } +} + /// A parallel mutable iterator over the entries of a `IndexMap`. /// /// This `struct` is created by the [`par_iter_mut`] method on [`IndexMap`] @@ -225,6 +258,37 @@ where } } +/// Parallel iterator methods and other parallel methods. +/// +/// The following methods **require crate feature `"rayon"`**. +/// +/// See also the `IntoParallelIterator` implementations. +impl Slice +where + K: Sync, + V: Sync, +{ + /// Return a parallel iterator over the keys of the map slice. + /// + /// While parallel iterators can process items in any order, their relative order + /// in the slice is still preserved for operations like `reduce` and `collect`. + pub fn par_keys(&self) -> ParKeys<'_, K, V> { + ParKeys { + entries: &self.entries, + } + } + + /// Return a parallel iterator over the values of the map slice. + /// + /// While parallel iterators can process items in any order, their relative order + /// in the slice is still preserved for operations like `reduce` and `collect`. + pub fn par_values(&self) -> ParValues<'_, K, V> { + ParValues { + entries: &self.entries, + } + } +} + impl IndexMap where K: Hash + Eq + Sync, @@ -331,6 +395,23 @@ where } } +/// Requires crate feature `"rayon"`. +impl Slice +where + K: Send, + V: Send, +{ + /// Return a parallel iterator over mutable references to the the values of the map slice. + /// + /// While parallel iterators can process items in any order, their relative order + /// in the slice is still preserved for operations like `reduce` and `collect`. + pub fn par_values_mut(&mut self) -> ParValuesMut<'_, K, V> { + ParValuesMut { + entries: &mut self.entries, + } + } +} + impl IndexMap where K: Hash + Eq + Send, diff --git a/src/rayon/set.rs b/src/rayon/set.rs index 6749dc0d..74d5e395 100644 --- a/src/rayon/set.rs +++ b/src/rayon/set.rs @@ -15,6 +15,7 @@ use core::fmt; use core::hash::{BuildHasher, Hash}; use core::ops::RangeBounds; +use crate::set::Slice; use crate::Entries; use crate::IndexSet; @@ -78,6 +79,21 @@ where } } +/// Requires crate feature `"rayon"`. +impl<'a, T> IntoParallelIterator for &'a Slice +where + T: Sync, +{ + type Item = &'a T; + type Iter = ParIter<'a, T>; + + fn into_par_iter(self) -> Self::Iter { + ParIter { + entries: &self.entries, + } + } +} + /// A parallel iterator over the items of a `IndexSet`. /// /// This `struct` is created by the [`par_iter`] method on [`IndexSet`] diff --git a/src/set/slice.rs b/src/set/slice.rs index 8962799f..2ab190d3 100644 --- a/src/set/slice.rs +++ b/src/set/slice.rs @@ -14,7 +14,7 @@ use core::ops::{self, Index}; /// and `Eq`, and it also implements `PartialOrd`, `Ord`, and `Hash`. #[repr(transparent)] pub struct Slice { - entries: [Bucket], + pub(crate) entries: [Bucket], } #[allow(unsafe_code)] From 45852b7a27f8eb545303c77ebbb5d8f8301eac2a Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 26 May 2021 12:25:09 -0700 Subject: [PATCH 060/236] `impl Index<(Bound, Bound)>` like Rust 1.53 --- src/map/slice.rs | 41 ++++++++++++++++++++++++++++++++++++++++- src/set/slice.rs | 25 ++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/map/slice.rs b/src/map/slice.rs index 741b06de..8677b279 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -1,9 +1,10 @@ use super::{Bucket, Entries, IndexMap, Iter, IterMut, Keys, Values, ValuesMut}; +use crate::util::simplify_range; use core::cmp::Ordering; use core::fmt; use core::hash::{Hash, Hasher}; -use core::ops::{self, Index, IndexMut}; +use core::ops::{self, Bound, Index, IndexMut}; /// A dynamically-sized slice of key-value pairs in an `IndexMap`. /// @@ -317,3 +318,41 @@ impl_index!( ops::RangeTo, ops::RangeToInclusive ); + +// NB: with MSRV 1.53, we can forward `Bound` pairs to direct slice indexing like other ranges + +impl Index<(Bound, Bound)> for IndexMap { + type Output = Slice; + + fn index(&self, range: (Bound, Bound)) -> &Self::Output { + let entries = self.as_entries(); + let range = simplify_range(range, entries.len()); + Slice::from_slice(&entries[range]) + } +} + +impl IndexMut<(Bound, Bound)> for IndexMap { + fn index_mut(&mut self, range: (Bound, Bound)) -> &mut Self::Output { + let entries = self.as_entries_mut(); + let range = simplify_range(range, entries.len()); + Slice::from_mut_slice(&mut entries[range]) + } +} + +impl Index<(Bound, Bound)> for Slice { + type Output = Slice; + + fn index(&self, range: (Bound, Bound)) -> &Self { + let entries = &self.entries; + let range = simplify_range(range, entries.len()); + Slice::from_slice(&entries[range]) + } +} + +impl IndexMut<(Bound, Bound)> for Slice { + fn index_mut(&mut self, range: (Bound, Bound)) -> &mut Self { + let entries = &mut self.entries; + let range = simplify_range(range, entries.len()); + Slice::from_mut_slice(&mut entries[range]) + } +} diff --git a/src/set/slice.rs b/src/set/slice.rs index 2ab190d3..8218e9e0 100644 --- a/src/set/slice.rs +++ b/src/set/slice.rs @@ -1,9 +1,10 @@ use super::{Bucket, Entries, IndexSet, Iter}; +use crate::util::simplify_range; use core::cmp::Ordering; use core::fmt; use core::hash::{Hash, Hasher}; -use core::ops::{self, Index}; +use core::ops::{self, Bound, Index}; /// A dynamically-sized slice of values in an `IndexSet`. /// @@ -191,3 +192,25 @@ impl_index!( ops::RangeTo, ops::RangeToInclusive ); + +// NB: with MSRV 1.53, we can forward `Bound` pairs to direct slice indexing like other ranges + +impl Index<(Bound, Bound)> for IndexSet { + type Output = Slice; + + fn index(&self, range: (Bound, Bound)) -> &Self::Output { + let entries = self.as_entries(); + let range = simplify_range(range, entries.len()); + Slice::from_slice(&entries[range]) + } +} + +impl Index<(Bound, Bound)> for Slice { + type Output = Self; + + fn index(&self, range: (Bound, Bound)) -> &Self::Output { + let entries = &self.entries; + let range = simplify_range(range, entries.len()); + Slice::from_slice(&entries[range]) + } +} From 7f9af49e7da31ec06becda0c7c339cbe4920a8e4 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 26 May 2021 13:12:07 -0700 Subject: [PATCH 061/236] Add `get_range` and `get_range_mut` --- src/map/slice.rs | 38 ++++++++++++++++++++++++++++++++++++-- src/set/slice.rs | 21 +++++++++++++++++++-- src/util.rs | 22 ++++++++++++++++++++++ 3 files changed, 77 insertions(+), 4 deletions(-) diff --git a/src/map/slice.rs b/src/map/slice.rs index 8677b279..7f091315 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -1,10 +1,10 @@ use super::{Bucket, Entries, IndexMap, Iter, IterMut, Keys, Values, ValuesMut}; -use crate::util::simplify_range; +use crate::util::{simplify_range, try_simplify_range}; use core::cmp::Ordering; use core::fmt; use core::hash::{Hash, Hasher}; -use core::ops::{self, Bound, Index, IndexMut}; +use core::ops::{self, Bound, Index, IndexMut, RangeBounds}; /// A dynamically-sized slice of key-value pairs in an `IndexMap`. /// @@ -43,6 +43,24 @@ impl IndexMap { pub fn as_mut_slice(&mut self) -> &mut Slice { Slice::from_mut_slice(self.as_entries_mut()) } + + /// Returns a slice of entries in the given range of indices. + /// + /// Valid indices are *0 <= index < self.len()* + pub fn get_range>(&self, range: R) -> Option<&Slice> { + let entries = self.as_entries(); + let range = try_simplify_range(range, entries.len())?; + entries.get(range).map(Slice::from_slice) + } + + /// Returns a mutable slice of entries in the given range of indices. + /// + /// Valid indices are *0 <= index < self.len()* + pub fn get_range_mut>(&mut self, range: R) -> Option<&mut Slice> { + let entries = self.as_entries_mut(); + let range = try_simplify_range(range, entries.len())?; + entries.get_mut(range).map(Slice::from_mut_slice) + } } impl<'a, K, V> Iter<'a, K, V> { @@ -90,6 +108,22 @@ impl Slice { self.entries.get_mut(index).map(Bucket::ref_mut) } + /// Returns a slice of key-value pairs in the given range of indices. + /// + /// Valid indices are *0 <= index < self.len()* + pub fn get_range>(&self, range: R) -> Option<&Self> { + let range = try_simplify_range(range, self.entries.len())?; + self.entries.get(range).map(Slice::from_slice) + } + + /// Returns a mutable slice of key-value pairs in the given range of indices. + /// + /// Valid indices are *0 <= index < self.len()* + pub fn get_range_mut>(&mut self, range: R) -> Option<&mut Self> { + let range = try_simplify_range(range, self.entries.len())?; + self.entries.get_mut(range).map(Slice::from_mut_slice) + } + /// Get the first key-value pair. pub fn first(&self) -> Option<(&K, &V)> { self.entries.first().map(Bucket::refs) diff --git a/src/set/slice.rs b/src/set/slice.rs index 8218e9e0..99ed5ce1 100644 --- a/src/set/slice.rs +++ b/src/set/slice.rs @@ -1,10 +1,10 @@ use super::{Bucket, Entries, IndexSet, Iter}; -use crate::util::simplify_range; +use crate::util::{simplify_range, try_simplify_range}; use core::cmp::Ordering; use core::fmt; use core::hash::{Hash, Hasher}; -use core::ops::{self, Bound, Index}; +use core::ops::{self, Bound, Index, RangeBounds}; /// A dynamically-sized slice of values in an `IndexSet`. /// @@ -32,6 +32,15 @@ impl IndexSet { pub fn as_slice(&self) -> &Slice { Slice::from_slice(self.as_entries()) } + + /// Returns a slice of values in the given range of indices. + /// + /// Valid indices are *0 <= index < self.len()* + pub fn get_range>(&self, range: R) -> Option<&Slice> { + let entries = self.as_entries(); + let range = try_simplify_range(range, entries.len())?; + entries.get(range).map(Slice::from_slice) + } } impl<'a, T> Iter<'a, T> { @@ -59,6 +68,14 @@ impl Slice { self.entries.get(index).map(Bucket::key_ref) } + /// Returns a slice of values in the given range of indices. + /// + /// Valid indices are *0 <= index < self.len()* + pub fn get_range>(&self, range: R) -> Option<&Self> { + let range = try_simplify_range(range, self.entries.len())?; + self.entries.get(range).map(Self::from_slice) + } + /// Get the first value. pub fn first(&self) -> Option<&T> { self.entries.first().map(Bucket::key_ref) diff --git a/src/util.rs b/src/util.rs index a24dfafd..377ff516 100644 --- a/src/util.rs +++ b/src/util.rs @@ -29,3 +29,25 @@ where } start..end } + +pub(crate) fn try_simplify_range(range: R, len: usize) -> Option> +where + R: RangeBounds, +{ + let start = match range.start_bound() { + Bound::Unbounded => 0, + Bound::Included(&i) if i <= len => i, + Bound::Excluded(&i) if i < len => i + 1, + _ => return None, + }; + let end = match range.end_bound() { + Bound::Unbounded => len, + Bound::Excluded(&i) if i <= len => i, + Bound::Included(&i) if i < len => i + 1, + _ => return None, + }; + if start > end { + return None; + } + Some(start..end) +} From 20c050683fbc94172c6f875d422367e5185778e4 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 27 May 2021 09:19:05 -0700 Subject: [PATCH 062/236] impl Default for &mut map::Slice, like normal slices --- src/map/slice.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/map/slice.rs b/src/map/slice.rs index 7f091315..6d5fa867 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -260,6 +260,12 @@ impl Default for &'_ Slice { } } +impl Default for &'_ mut Slice { + fn default() -> Self { + Slice::from_mut_slice(&mut []) + } +} + impl fmt::Debug for Slice { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_list().entries(self).finish() From 949c9a9159aedc7ac232e3ec1c928b4ad24a8f99 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 27 May 2021 11:21:59 -0700 Subject: [PATCH 063/236] Add tests of slice indexing --- src/map/slice.rs | 116 +++++++++++++++++++++++++++++++++++++++++++++++ src/set/slice.rs | 50 ++++++++++++++++++++ 2 files changed, 166 insertions(+) diff --git a/src/map/slice.rs b/src/map/slice.rs index 6d5fa867..80d3f89e 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -396,3 +396,119 @@ impl IndexMut<(Bound, Bound)> for Slice { Slice::from_mut_slice(&mut entries[range]) } } + +#[cfg(test)] +mod tests { + use super::*; + use alloc::vec::Vec; + + #[test] + fn slice_index() { + fn check( + vec_slice: &[(i32, i32)], + map_slice: &Slice, + sub_slice: &Slice, + ) { + assert_eq!(map_slice as *const _, sub_slice as *const _); + itertools::assert_equal( + vec_slice.iter().copied(), + map_slice.iter().map(|(&k, &v)| (k, v)), + ); + itertools::assert_equal(vec_slice.iter().map(|(k, _)| k), map_slice.keys()); + itertools::assert_equal(vec_slice.iter().map(|(_, v)| v), map_slice.values()); + } + + let vec: Vec<(i32, i32)> = (0..10).map(|i| (i, i * i)).collect(); + let map: IndexMap = vec.iter().cloned().collect(); + let slice = map.as_slice(); + + // RangeFull + check(&vec[..], &map[..], &slice[..]); + + for i in 0usize..10 { + // Index + assert_eq!(vec[i].1, map[i]); + assert_eq!(vec[i].1, slice[i]); + assert_eq!(map[&(i as i32)], map[i]); + assert_eq!(map[&(i as i32)], slice[i]); + + // RangeFrom + check(&vec[i..], &map[i..], &slice[i..]); + + // RangeTo + check(&vec[..i], &map[..i], &slice[..i]); + + // RangeToInclusive + check(&vec[..=i], &map[..=i], &slice[..=i]); + + // (Bound, Bound) + let bounds = (Bound::Excluded(i), Bound::Unbounded); + check(&vec[i + 1..], &map[bounds], &slice[bounds]); + + for j in i..=10 { + // Range + check(&vec[i..j], &map[i..j], &slice[i..j]); + } + + for j in i..10 { + // RangeInclusive + check(&vec[i..=j], &map[i..=j], &slice[i..=j]); + } + } + } + + #[test] + fn slice_index_mut() { + fn check_mut( + vec_slice: &[(i32, i32)], + map_slice: &mut Slice, + sub_slice: &mut Slice, + ) { + assert_eq!(map_slice, sub_slice); + itertools::assert_equal( + vec_slice.iter().copied(), + map_slice.iter_mut().map(|(&k, &mut v)| (k, v)), + ); + itertools::assert_equal( + vec_slice.iter().map(|&(_, v)| v), + map_slice.values_mut().map(|&mut v| v), + ); + } + + let vec: Vec<(i32, i32)> = (0..10).map(|i| (i, i * i)).collect(); + let mut map: IndexMap = vec.iter().cloned().collect(); + let mut map2 = map.clone(); + let slice = map2.as_mut_slice(); + + // RangeFull + check_mut(&vec[..], &mut map[..], &mut slice[..]); + + for i in 0usize..10 { + // IndexMut + assert_eq!(&mut map[i], &mut slice[i]); + + // RangeFrom + check_mut(&vec[i..], &mut map[i..], &mut slice[i..]); + + // RangeTo + check_mut(&vec[..i], &mut map[..i], &mut slice[..i]); + + // RangeToInclusive + check_mut(&vec[..=i], &mut map[..=i], &mut slice[..=i]); + + // (Bound, Bound) + let bounds = (Bound::Excluded(i), Bound::Unbounded); + check_mut(&vec[i + 1..], &mut map[bounds], &mut slice[bounds]); + + for j in i..=10 { + // Range + check_mut(&vec[i..j], &mut map[i..j], &mut slice[i..j]); + } + + for j in i..10 { + // RangeInclusive + check_mut(&vec[i..=j], &mut map[i..=j], &mut slice[i..=j]); + } + } + } +} diff --git a/src/set/slice.rs b/src/set/slice.rs index 99ed5ce1..97b9682c 100644 --- a/src/set/slice.rs +++ b/src/set/slice.rs @@ -231,3 +231,53 @@ impl Index<(Bound, Bound)> for Slice { Slice::from_slice(&entries[range]) } } + +#[cfg(test)] +mod tests { + use super::*; + use alloc::vec::Vec; + + #[test] + fn slice_index() { + fn check(vec_slice: &[i32], set_slice: &Slice, sub_slice: &Slice) { + assert_eq!(set_slice as *const _, sub_slice as *const _); + itertools::assert_equal(vec_slice, set_slice); + } + + let vec: Vec = (0..10).map(|i| i * i).collect(); + let set: IndexSet = vec.iter().cloned().collect(); + let slice = set.as_slice(); + + // RangeFull + check(&vec[..], &set[..], &slice[..]); + + for i in 0usize..10 { + // Index + assert_eq!(vec[i], set[i]); + assert_eq!(vec[i], slice[i]); + + // RangeFrom + check(&vec[i..], &set[i..], &slice[i..]); + + // RangeTo + check(&vec[..i], &set[..i], &slice[..i]); + + // RangeToInclusive + check(&vec[..=i], &set[..=i], &slice[..=i]); + + // (Bound, Bound) + let bounds = (Bound::Excluded(i), Bound::Unbounded); + check(&vec[i + 1..], &set[bounds], &slice[bounds]); + + for j in i..=10 { + // Range + check(&vec[i..j], &set[i..j], &slice[i..j]); + } + + for j in i..10 { + // RangeInclusive + check(&vec[i..=j], &set[i..=j], &slice[i..=j]); + } + } + } +} From e9024a39c97a4cbfb939dcd0af38bfff59b33e3b Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 27 May 2021 11:38:59 -0700 Subject: [PATCH 064/236] Move some slice methods for better doc order --- src/map.rs | 54 +++++++++++++++++++++++++++++++++++++++++++++++- src/map/slice.rs | 50 ++------------------------------------------ src/set.rs | 26 +++++++++++++++++++++++ src/set/slice.rs | 25 +--------------------- 4 files changed, 82 insertions(+), 73 deletions(-) diff --git a/src/map.rs b/src/map.rs index 38cf326b..86d344d0 100644 --- a/src/map.rs +++ b/src/map.rs @@ -23,7 +23,7 @@ use std::collections::hash_map::RandomState; use self::core::IndexMapCore; use crate::equivalent::Equivalent; -use crate::util::third; +use crate::util::{third, try_simplify_range}; use crate::{Bucket, Entries, HashValue}; pub use self::core::{Entry, OccupiedEntry, VacantEntry}; @@ -760,6 +760,20 @@ where } impl IndexMap { + /// Returns a slice of all the key-value pairs in the map. + /// + /// Computes in **O(1)** time. + pub fn as_slice(&self) -> &Slice { + Slice::from_slice(self.as_entries()) + } + + /// Returns a mutable slice of all the key-value pairs in the map. + /// + /// Computes in **O(1)** time. + pub fn as_mut_slice(&mut self) -> &mut Slice { + Slice::from_mut_slice(self.as_entries_mut()) + } + /// Get a key-value pair by index /// /// Valid indices are *0 <= index < self.len()* @@ -778,6 +792,28 @@ impl IndexMap { self.as_entries_mut().get_mut(index).map(Bucket::ref_mut) } + /// Returns a slice of key-value pairs in the given range of indices. + /// + /// Valid indices are *0 <= index < self.len()* + /// + /// Computes in **O(1)** time. + pub fn get_range>(&self, range: R) -> Option<&Slice> { + let entries = self.as_entries(); + let range = try_simplify_range(range, entries.len())?; + entries.get(range).map(Slice::from_slice) + } + + /// Returns a mutable slice of key-value pairs in the given range of indices. + /// + /// Valid indices are *0 <= index < self.len()* + /// + /// Computes in **O(1)** time. + pub fn get_range_mut>(&mut self, range: R) -> Option<&mut Slice> { + let entries = self.as_entries_mut(); + let range = try_simplify_range(range, entries.len())?; + entries.get_mut(range).map(Slice::from_mut_slice) + } + /// Get the first key-value pair /// /// Computes in **O(1)** time. @@ -1047,6 +1083,13 @@ pub struct Iter<'a, K, V> { iter: SliceIter<'a, Bucket>, } +impl<'a, K, V> Iter<'a, K, V> { + /// Returns a slice of the remaining entries in the iterator. + pub fn as_slice(&self) -> &'a Slice { + Slice::from_slice(self.iter.as_slice()) + } +} + impl<'a, K, V> Iterator for Iter<'a, K, V> { type Item = (&'a K, &'a V); @@ -1091,6 +1134,15 @@ pub struct IterMut<'a, K, V> { iter: SliceIterMut<'a, Bucket>, } +impl<'a, K, V> IterMut<'a, K, V> { + /// Returns a slice of the remaining entries in the iterator. + /// + /// To avoid creating `&mut` references that alias, this is forced to consume the iterator. + pub fn into_slice(self) -> &'a mut Slice { + Slice::from_mut_slice(self.iter.into_slice()) + } +} + impl<'a, K, V> Iterator for IterMut<'a, K, V> { type Item = (&'a K, &'a mut V); diff --git a/src/map/slice.rs b/src/map/slice.rs index 80d3f89e..8f642386 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -20,65 +20,19 @@ pub struct Slice { #[allow(unsafe_code)] impl Slice { - fn from_slice(entries: &[Bucket]) -> &Self { + pub(super) fn from_slice(entries: &[Bucket]) -> &Self { // SAFETY: `Slice` is a transparent wrapper around `[Bucket]`, // and the lifetimes are bound together by this function's signature. unsafe { &*(entries as *const [Bucket] as *const Self) } } - fn from_mut_slice(entries: &mut [Bucket]) -> &mut Self { + pub(super) fn from_mut_slice(entries: &mut [Bucket]) -> &mut Self { // SAFETY: `Slice` is a transparent wrapper around `[Bucket]`, // and the lifetimes are bound together by this function's signature. unsafe { &mut *(entries as *mut [Bucket] as *mut Self) } } } -impl IndexMap { - /// Returns a slice of all the entries in the map. - pub fn as_slice(&self) -> &Slice { - Slice::from_slice(self.as_entries()) - } - - /// Returns a mutable slice of all the entries in the map. - pub fn as_mut_slice(&mut self) -> &mut Slice { - Slice::from_mut_slice(self.as_entries_mut()) - } - - /// Returns a slice of entries in the given range of indices. - /// - /// Valid indices are *0 <= index < self.len()* - pub fn get_range>(&self, range: R) -> Option<&Slice> { - let entries = self.as_entries(); - let range = try_simplify_range(range, entries.len())?; - entries.get(range).map(Slice::from_slice) - } - - /// Returns a mutable slice of entries in the given range of indices. - /// - /// Valid indices are *0 <= index < self.len()* - pub fn get_range_mut>(&mut self, range: R) -> Option<&mut Slice> { - let entries = self.as_entries_mut(); - let range = try_simplify_range(range, entries.len())?; - entries.get_mut(range).map(Slice::from_mut_slice) - } -} - -impl<'a, K, V> Iter<'a, K, V> { - /// Returns a slice of the remaining entries in the iterator. - pub fn as_slice(&self) -> &'a Slice { - Slice::from_slice(self.iter.as_slice()) - } -} - -impl<'a, K, V> IterMut<'a, K, V> { - /// Returns a slice of the remaining entries in the iterator. - /// - /// To avoid creating `&mut` references that alias, this is forced to consume the iterator. - pub fn into_slice(self) -> &'a mut Slice { - Slice::from_mut_slice(self.iter.into_slice()) - } -} - impl Slice { /// Return the number of key-value pairs in the map slice. #[inline] diff --git a/src/set.rs b/src/set.rs index 4484693a..c1468974 100644 --- a/src/set.rs +++ b/src/set.rs @@ -10,6 +10,7 @@ pub use crate::rayon::set as rayon; #[cfg(feature = "std")] use std::collections::hash_map::RandomState; +use crate::util::try_simplify_range; use crate::vec::{self, Vec}; use core::cmp::Ordering; use core::fmt; @@ -654,6 +655,13 @@ where } impl IndexSet { + /// Returns a slice of all the values in the set. + /// + /// Computes in **O(1)** time. + pub fn as_slice(&self) -> &Slice { + Slice::from_slice(self.as_entries()) + } + /// Get a value by index /// /// Valid indices are *0 <= index < self.len()* @@ -663,6 +671,17 @@ impl IndexSet { self.as_entries().get(index).map(Bucket::key_ref) } + /// Returns a slice of values in the given range of indices. + /// + /// Valid indices are *0 <= index < self.len()* + /// + /// Computes in **O(1)** time. + pub fn get_range>(&self, range: R) -> Option<&Slice> { + let entries = self.as_entries(); + let range = try_simplify_range(range, entries.len())?; + entries.get(range).map(Slice::from_slice) + } + /// Get the first value /// /// Computes in **O(1)** time. @@ -798,6 +817,13 @@ pub struct Iter<'a, T> { iter: SliceIter<'a, Bucket>, } +impl<'a, T> Iter<'a, T> { + /// Returns a slice of the remaining entries in the iterator. + pub fn as_slice(&self) -> &'a Slice { + Slice::from_slice(self.iter.as_slice()) + } +} + impl<'a, T> Iterator for Iter<'a, T> { type Item = &'a T; diff --git a/src/set/slice.rs b/src/set/slice.rs index 97b9682c..ba0d7ec4 100644 --- a/src/set/slice.rs +++ b/src/set/slice.rs @@ -20,36 +20,13 @@ pub struct Slice { #[allow(unsafe_code)] impl Slice { - fn from_slice(entries: &[Bucket]) -> &Self { + pub(super) fn from_slice(entries: &[Bucket]) -> &Self { // SAFETY: `Slice` is a transparent wrapper around `[Bucket]`, // and the lifetimes are bound together by this function's signature. unsafe { &*(entries as *const [Bucket] as *const Self) } } } -impl IndexSet { - /// Returns a slice of all the values in the set. - pub fn as_slice(&self) -> &Slice { - Slice::from_slice(self.as_entries()) - } - - /// Returns a slice of values in the given range of indices. - /// - /// Valid indices are *0 <= index < self.len()* - pub fn get_range>(&self, range: R) -> Option<&Slice> { - let entries = self.as_entries(); - let range = try_simplify_range(range, entries.len())?; - entries.get(range).map(Slice::from_slice) - } -} - -impl<'a, T> Iter<'a, T> { - /// Returns a slice of the remaining entries in the iterator. - pub fn as_slice(&self) -> &'a Slice { - Slice::from_slice(self.iter.as_slice()) - } -} - impl Slice { /// Return the number of elements in the set slice. pub fn len(&self) -> usize { From 7304afcbd87a2eeecf6606e579a04745dfa65c79 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 27 May 2021 12:19:54 -0700 Subject: [PATCH 065/236] impl Serialize for {map,set}::Slice --- src/serde_seq.rs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/serde_seq.rs b/src/serde_seq.rs index 7c89a6e5..b8bd8203 100644 --- a/src/serde_seq.rs +++ b/src/serde_seq.rs @@ -28,6 +28,42 @@ use core::hash::{BuildHasher, Hash}; use core::marker::PhantomData; use crate::IndexMap; +use crate::map::Slice as MapSlice; +use crate::set::Slice as SetSlice; + +/// Serializes a `map::Slice` as an ordered sequence. +/// +/// This behaves like [`crate::serde_seq`] for `IndexMap`, serializing a sequence +/// of `(key, value)` pairs, rather than as a map that might not preserver order. +/// +/// Requires crate feature `"serde"` or `"serde-1"` +impl Serialize for MapSlice +where + K: Serialize, + V: Serialize, +{ + fn serialize(&self, serializer: T) -> Result + where + T: Serializer, + { + serializer.collect_seq(self) + } +} + +/// Serializes a `set::Slice` as an ordered sequence. +/// +/// Requires crate feature `"serde"` or `"serde-1"` +impl Serialize for SetSlice +where + T: Serialize, +{ + fn serialize(&self, serializer: Se) -> Result + where + Se: Serializer, + { + serializer.collect_seq(self) + } +} /// Serializes an `IndexMap` as an ordered sequence. /// From 0241a1863c2095aea5aab43434ca32e66636f167 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 1 Apr 2022 15:29:32 -0700 Subject: [PATCH 066/236] Remove outdated comment on get_index_mut --- src/map/slice.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/map/slice.rs b/src/map/slice.rs index 8f642386..0be11332 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -57,8 +57,6 @@ impl Slice { /// /// Valid indices are *0 <= index < self.len()* pub fn get_index_mut(&mut self, index: usize) -> Option<(&K, &mut V)> { - // NB: we're not returning `&mut K` like `IndexMap::get_index_mut`, - // because that was a mistake that should have required `MutableKeys`. self.entries.get_mut(index).map(Bucket::ref_mut) } From 7242c540ed2db10f050ed382f6de6b0adbf49a75 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 1 Apr 2022 15:30:04 -0700 Subject: [PATCH 067/236] Use built-in indexing for Bound pairs --- src/map/slice.rs | 43 +++---------------------------------------- src/set/slice.rs | 27 +++------------------------ 2 files changed, 6 insertions(+), 64 deletions(-) diff --git a/src/map/slice.rs b/src/map/slice.rs index 0be11332..077aebbe 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -1,5 +1,5 @@ use super::{Bucket, Entries, IndexMap, Iter, IterMut, Keys, Values, ValuesMut}; -use crate::util::{simplify_range, try_simplify_range}; +use crate::util::try_simplify_range; use core::cmp::Ordering; use core::fmt; @@ -308,47 +308,10 @@ impl_index!( ops::RangeFull, ops::RangeInclusive, ops::RangeTo, - ops::RangeToInclusive + ops::RangeToInclusive, + (Bound, Bound) ); -// NB: with MSRV 1.53, we can forward `Bound` pairs to direct slice indexing like other ranges - -impl Index<(Bound, Bound)> for IndexMap { - type Output = Slice; - - fn index(&self, range: (Bound, Bound)) -> &Self::Output { - let entries = self.as_entries(); - let range = simplify_range(range, entries.len()); - Slice::from_slice(&entries[range]) - } -} - -impl IndexMut<(Bound, Bound)> for IndexMap { - fn index_mut(&mut self, range: (Bound, Bound)) -> &mut Self::Output { - let entries = self.as_entries_mut(); - let range = simplify_range(range, entries.len()); - Slice::from_mut_slice(&mut entries[range]) - } -} - -impl Index<(Bound, Bound)> for Slice { - type Output = Slice; - - fn index(&self, range: (Bound, Bound)) -> &Self { - let entries = &self.entries; - let range = simplify_range(range, entries.len()); - Slice::from_slice(&entries[range]) - } -} - -impl IndexMut<(Bound, Bound)> for Slice { - fn index_mut(&mut self, range: (Bound, Bound)) -> &mut Self { - let entries = &mut self.entries; - let range = simplify_range(range, entries.len()); - Slice::from_mut_slice(&mut entries[range]) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/set/slice.rs b/src/set/slice.rs index ba0d7ec4..1e5a5bb2 100644 --- a/src/set/slice.rs +++ b/src/set/slice.rs @@ -1,5 +1,5 @@ use super::{Bucket, Entries, IndexSet, Iter}; -use crate::util::{simplify_range, try_simplify_range}; +use crate::util::try_simplify_range; use core::cmp::Ordering; use core::fmt; @@ -184,31 +184,10 @@ impl_index!( ops::RangeFull, ops::RangeInclusive, ops::RangeTo, - ops::RangeToInclusive + ops::RangeToInclusive, + (Bound, Bound) ); -// NB: with MSRV 1.53, we can forward `Bound` pairs to direct slice indexing like other ranges - -impl Index<(Bound, Bound)> for IndexSet { - type Output = Slice; - - fn index(&self, range: (Bound, Bound)) -> &Self::Output { - let entries = self.as_entries(); - let range = simplify_range(range, entries.len()); - Slice::from_slice(&entries[range]) - } -} - -impl Index<(Bound, Bound)> for Slice { - type Output = Self; - - fn index(&self, range: (Bound, Bound)) -> &Self::Output { - let entries = &self.entries; - let range = simplify_range(range, entries.len()); - Slice::from_slice(&entries[range]) - } -} - #[cfg(test)] mod tests { use super::*; From 9f2b14d678eff8e8d317a758ede7df7705770749 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 1 Apr 2022 16:50:44 -0700 Subject: [PATCH 068/236] Hide unnecessary iterator visibility --- src/map.rs | 4 ++-- src/set.rs | 13 +++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/map.rs b/src/map.rs index 86d344d0..7c72e9bf 100644 --- a/src/map.rs +++ b/src/map.rs @@ -884,7 +884,7 @@ impl IndexMap { /// [`keys`]: struct.IndexMap.html#method.keys /// [`IndexMap`]: struct.IndexMap.html pub struct Keys<'a, K, V> { - pub(crate) iter: SliceIter<'a, Bucket>, + iter: SliceIter<'a, Bucket>, } impl<'a, K, V> Iterator for Keys<'a, K, V> { @@ -1176,7 +1176,7 @@ impl fmt::Debug for IterMut<'_, K, V> { /// [`into_iter`]: struct.IndexMap.html#method.into_iter /// [`IndexMap`]: struct.IndexMap.html pub struct IntoIter { - pub(crate) iter: vec::IntoIter>, + iter: vec::IntoIter>, } impl Iterator for IntoIter { diff --git a/src/set.rs b/src/set.rs index c1468974..a115c262 100644 --- a/src/set.rs +++ b/src/set.rs @@ -607,8 +607,10 @@ where where F: FnMut(&T, &T) -> Ordering, { + let mut entries = self.into_entries(); + entries.sort_by(move |a, b| cmp(&a.key, &b.key)); IntoIter { - iter: self.map.sorted_by(move |a, _, b, _| cmp(a, b)).iter, + iter: entries.into_iter(), } } @@ -638,11 +640,10 @@ where where F: FnMut(&T, &T) -> Ordering, { + let mut entries = self.into_entries(); + entries.sort_unstable_by(move |a, b| cmp(&a.key, &b.key)); IntoIter { - iter: self - .map - .sorted_unstable_by(move |a, _, b, _| cmp(a, b)) - .iter, + iter: entries.into_iter(), } } @@ -907,7 +908,7 @@ impl IntoIterator for IndexSet { fn into_iter(self) -> Self::IntoIter { IntoIter { - iter: self.map.into_iter().iter, + iter: self.into_entries().into_iter(), } } } From 8c49292fc7f840b341fda9ca7650c8d2944d8b98 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 1 Apr 2022 17:08:40 -0700 Subject: [PATCH 069/236] Add boxed slices, including owned iterators --- src/map.rs | 8 ++++++++ src/map/slice.rs | 50 +++++++++++++++++++++++++++++++++++++++++++----- src/rayon/map.rs | 17 ++++++++++++++++ src/rayon/set.rs | 16 ++++++++++++++++ src/set.rs | 8 ++++++++ src/set/slice.rs | 31 +++++++++++++++++++++++++++--- 6 files changed, 122 insertions(+), 8 deletions(-) diff --git a/src/map.rs b/src/map.rs index 7c72e9bf..93622bc6 100644 --- a/src/map.rs +++ b/src/map.rs @@ -17,6 +17,7 @@ use ::core::hash::{BuildHasher, Hash, Hasher}; use ::core::iter::FusedIterator; use ::core::ops::{Index, IndexMut, RangeBounds}; use ::core::slice::{Iter as SliceIter, IterMut as SliceIterMut}; +use alloc::boxed::Box; #[cfg(feature = "std")] use std::collections::hash_map::RandomState; @@ -774,6 +775,13 @@ impl IndexMap { Slice::from_mut_slice(self.as_entries_mut()) } + /// Converts into a boxed slice of all the key-value pairs in the map. + /// + /// Note that this will drop the inner hash table and any excess capacity. + pub fn into_boxed_slice(self) -> Box> { + Slice::from_boxed(self.into_entries().into_boxed_slice()) + } + /// Get a key-value pair by index /// /// Valid indices are *0 <= index < self.len()* diff --git a/src/map/slice.rs b/src/map/slice.rs index 077aebbe..9fe1be6b 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -1,6 +1,11 @@ -use super::{Bucket, Entries, IndexMap, Iter, IterMut, Keys, Values, ValuesMut}; +use super::{ + Bucket, Entries, IndexMap, IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Values, + ValuesMut, +}; use crate::util::try_simplify_range; +use alloc::boxed::Box; +use alloc::vec::Vec; use core::cmp::Ordering; use core::fmt; use core::hash::{Hash, Hasher}; @@ -18,22 +23,32 @@ pub struct Slice { pub(crate) entries: [Bucket], } +// SAFETY: `Slice` is a transparent wrapper around `[Bucket]`, +// and reference lifetimes are bound together in function signatures. #[allow(unsafe_code)] impl Slice { pub(super) fn from_slice(entries: &[Bucket]) -> &Self { - // SAFETY: `Slice` is a transparent wrapper around `[Bucket]`, - // and the lifetimes are bound together by this function's signature. unsafe { &*(entries as *const [Bucket] as *const Self) } } pub(super) fn from_mut_slice(entries: &mut [Bucket]) -> &mut Self { - // SAFETY: `Slice` is a transparent wrapper around `[Bucket]`, - // and the lifetimes are bound together by this function's signature. unsafe { &mut *(entries as *mut [Bucket] as *mut Self) } } + + pub(super) fn from_boxed(entries: Box<[Bucket]>) -> Box { + unsafe { Box::from_raw(Box::into_raw(entries) as *mut Self) } + } + + fn into_boxed(self: Box) -> Box<[Bucket]> { + unsafe { Box::from_raw(Box::into_raw(self) as *mut [Bucket]) } + } } impl Slice { + pub(crate) fn into_entries(self: Box) -> Vec> { + self.into_boxed().into_vec() + } + /// Return the number of key-value pairs in the map slice. #[inline] pub fn len(&self) -> usize { @@ -173,6 +188,13 @@ impl Slice { } } + /// Return an owning iterator over the keys of the map slice. + pub fn into_keys(self: Box) -> IntoKeys { + IntoKeys { + iter: self.into_entries().into_iter(), + } + } + /// Return an iterator over the values of the map slice. pub fn values(&self) -> Values<'_, K, V> { Values { @@ -186,6 +208,13 @@ impl Slice { iter: self.entries.iter_mut(), } } + + /// Return an owning iterator over the values of the map slice. + pub fn into_values(self: Box) -> IntoValues { + IntoValues { + iter: self.into_entries().into_iter(), + } + } } impl<'a, K, V> IntoIterator for &'a Slice { @@ -206,6 +235,17 @@ impl<'a, K, V> IntoIterator for &'a mut Slice { } } +impl IntoIterator for Box> { + type IntoIter = IntoIter; + type Item = (K, V); + + fn into_iter(self) -> Self::IntoIter { + IntoIter { + iter: self.into_entries().into_iter(), + } + } +} + impl Default for &'_ Slice { fn default() -> Self { Slice::from_slice(&[]) diff --git a/src/rayon/map.rs b/src/rayon/map.rs index 021b63ba..7559d549 100644 --- a/src/rayon/map.rs +++ b/src/rayon/map.rs @@ -10,6 +10,7 @@ use rayon::iter::plumbing::{Consumer, ProducerCallback, UnindexedConsumer}; use rayon::prelude::*; use crate::vec::Vec; +use alloc::boxed::Box; use core::cmp::Ordering; use core::fmt; use core::hash::{BuildHasher, Hash}; @@ -36,6 +37,22 @@ where } } +/// Requires crate feature `"rayon"`. +impl IntoParallelIterator for Box> +where + K: Send, + V: Send, +{ + type Item = (K, V); + type Iter = IntoParIter; + + fn into_par_iter(self) -> Self::Iter { + IntoParIter { + entries: self.into_entries(), + } + } +} + /// A parallel owning iterator over the entries of a `IndexMap`. /// /// This `struct` is created by the [`into_par_iter`] method on [`IndexMap`] diff --git a/src/rayon/set.rs b/src/rayon/set.rs index 74d5e395..0dc553fc 100644 --- a/src/rayon/set.rs +++ b/src/rayon/set.rs @@ -10,6 +10,7 @@ use rayon::iter::plumbing::{Consumer, ProducerCallback, UnindexedConsumer}; use rayon::prelude::*; use crate::vec::Vec; +use alloc::boxed::Box; use core::cmp::Ordering; use core::fmt; use core::hash::{BuildHasher, Hash}; @@ -36,6 +37,21 @@ where } } +/// Requires crate feature `"rayon"`. +impl IntoParallelIterator for Box> +where + T: Send, +{ + type Item = T; + type Iter = IntoParIter; + + fn into_par_iter(self) -> Self::Iter { + IntoParIter { + entries: self.into_entries(), + } + } +} + /// A parallel owning iterator over the items of a `IndexSet`. /// /// This `struct` is created by the [`into_par_iter`] method on [`IndexSet`] diff --git a/src/set.rs b/src/set.rs index a115c262..bdcbaffb 100644 --- a/src/set.rs +++ b/src/set.rs @@ -12,6 +12,7 @@ use std::collections::hash_map::RandomState; use crate::util::try_simplify_range; use crate::vec::{self, Vec}; +use alloc::boxed::Box; use core::cmp::Ordering; use core::fmt; use core::hash::{BuildHasher, Hash}; @@ -663,6 +664,13 @@ impl IndexSet { Slice::from_slice(self.as_entries()) } + /// Converts into a boxed slice of all the values in the set. + /// + /// Note that this will drop the inner hash table and any excess capacity. + pub fn into_boxed_slice(self) -> Box> { + Slice::from_boxed(self.into_entries().into_boxed_slice()) + } + /// Get a value by index /// /// Valid indices are *0 <= index < self.len()* diff --git a/src/set/slice.rs b/src/set/slice.rs index 1e5a5bb2..0039b4a7 100644 --- a/src/set/slice.rs +++ b/src/set/slice.rs @@ -1,6 +1,8 @@ -use super::{Bucket, Entries, IndexSet, Iter}; +use super::{Bucket, Entries, IndexSet, IntoIter, Iter}; use crate::util::try_simplify_range; +use alloc::boxed::Box; +use alloc::vec::Vec; use core::cmp::Ordering; use core::fmt; use core::hash::{Hash, Hasher}; @@ -18,16 +20,28 @@ pub struct Slice { pub(crate) entries: [Bucket], } +// SAFETY: `Slice` is a transparent wrapper around `[Bucket]`, +// and reference lifetimes are bound together in function signatures. #[allow(unsafe_code)] impl Slice { pub(super) fn from_slice(entries: &[Bucket]) -> &Self { - // SAFETY: `Slice` is a transparent wrapper around `[Bucket]`, - // and the lifetimes are bound together by this function's signature. unsafe { &*(entries as *const [Bucket] as *const Self) } } + + pub(super) fn from_boxed(entries: Box<[Bucket]>) -> Box { + unsafe { Box::from_raw(Box::into_raw(entries) as *mut Self) } + } + + fn into_boxed(self: Box) -> Box<[Bucket]> { + unsafe { Box::from_raw(Box::into_raw(self) as *mut [Bucket]) } + } } impl Slice { + pub(crate) fn into_entries(self: Box) -> Vec> { + self.into_boxed().into_vec() + } + /// Return the number of elements in the set slice. pub fn len(&self) -> usize { self.entries.len() @@ -108,6 +122,17 @@ impl<'a, T> IntoIterator for &'a Slice { } } +impl IntoIterator for Box> { + type IntoIter = IntoIter; + type Item = T; + + fn into_iter(self) -> Self::IntoIter { + IntoIter { + iter: self.into_entries().into_iter(), + } + } +} + impl Default for &'_ Slice { fn default() -> Self { Slice::from_slice(&[]) From 2d3b165e1eff0a2bd3957e9c4864285c95d2182c Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 1 Apr 2022 17:56:44 -0700 Subject: [PATCH 070/236] impl {Default,Clone,From<&Slice>} for Box --- src/map/slice.rs | 18 ++++++++++++++++++ src/set/slice.rs | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/map/slice.rs b/src/map/slice.rs index 9fe1be6b..ae9030ec 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -258,6 +258,24 @@ impl Default for &'_ mut Slice { } } +impl Default for Box> { + fn default() -> Self { + Slice::from_boxed(Box::default()) + } +} + +impl Clone for Box> { + fn clone(&self) -> Self { + Slice::from_boxed(self.entries.to_vec().into_boxed_slice()) + } +} + +impl From<&Slice> for Box> { + fn from(slice: &Slice) -> Self { + Slice::from_boxed(slice.entries.to_vec().into_boxed_slice()) + } +} + impl fmt::Debug for Slice { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_list().entries(self).finish() diff --git a/src/set/slice.rs b/src/set/slice.rs index 0039b4a7..203ce126 100644 --- a/src/set/slice.rs +++ b/src/set/slice.rs @@ -139,6 +139,24 @@ impl Default for &'_ Slice { } } +impl Default for Box> { + fn default() -> Self { + Slice::from_boxed(Box::default()) + } +} + +impl Clone for Box> { + fn clone(&self) -> Self { + Slice::from_boxed(self.entries.to_vec().into_boxed_slice()) + } +} + +impl From<&Slice> for Box> { + fn from(slice: &Slice) -> Self { + Slice::from_boxed(slice.entries.to_vec().into_boxed_slice()) + } +} + impl fmt::Debug for Slice { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_list().entries(self).finish() From 49836c639475fd15ddb2916cbc63b2d6028f2f8e Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Mon, 4 Apr 2022 11:59:11 -0700 Subject: [PATCH 071/236] use more a direct Box from &Slice --- src/map/slice.rs | 2 +- src/set/slice.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/map/slice.rs b/src/map/slice.rs index ae9030ec..07c6705a 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -272,7 +272,7 @@ impl Clone for Box> { impl From<&Slice> for Box> { fn from(slice: &Slice) -> Self { - Slice::from_boxed(slice.entries.to_vec().into_boxed_slice()) + Slice::from_boxed(Box::from(&slice.entries)) } } diff --git a/src/set/slice.rs b/src/set/slice.rs index 203ce126..dd317ebd 100644 --- a/src/set/slice.rs +++ b/src/set/slice.rs @@ -153,7 +153,7 @@ impl Clone for Box> { impl From<&Slice> for Box> { fn from(slice: &Slice) -> Self { - Slice::from_boxed(slice.entries.to_vec().into_boxed_slice()) + Slice::from_boxed(Box::from(&slice.entries)) } } From 4750dc7dfc28300a37cc8b2a2c6d536fb8b85ecd Mon Sep 17 00:00:00 2001 From: Stiopa Koltsov Date: Fri, 15 Apr 2022 23:19:49 +0100 Subject: [PATCH 072/236] Documentation assertion in erase_index function --- src/map/core.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/map/core.rs b/src/map/core.rs index 6479135d..50ebebea 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -44,7 +44,8 @@ fn equivalent<'a, K, V, Q: ?Sized + Equivalent>( #[inline] fn erase_index(table: &mut RawTable, hash: HashValue, index: usize) { - table.erase_entry(hash.get(), move |&i| i == index); + let erased = table.erase_entry(hash.get(), move |&i| i == index); + debug_assert!(erased); } #[inline] From eac27f11dd1186c7a7e0a7dcd3a5896c3a9c6b22 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 6 May 2022 18:02:14 -0700 Subject: [PATCH 073/236] Add a release note for Slice --- RELEASES.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index f27a0bc8..c1efa646 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -19,6 +19,11 @@ - The new `IndexMap::shrink_to` and `IndexSet::shrink_to` methods shrink the capacity with a lower bound. + - The new `map::Slice` and `set::Slice` offer a linear view of maps + and sets, behaving a lot like normal `[(K, V)]` and `[T]` slices. Notably, + comparison traits like `Eq` only consider items in order, rather than hash + lookups, and slices even implement `Hash`. + - 1.8.1 - The new `IndexSet::replace_full` will return the index of the item along From 8a571c6d68cb38c283d563ff6972613e0eea4111 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 6 May 2022 18:09:53 -0700 Subject: [PATCH 074/236] Bump MSRV to 1.56.1, matching hashbrown as of 0.12.1 --- .github/workflows/ci.yml | 4 ++-- Cargo.toml | 2 +- README.md | 2 +- RELEASES.md | 2 +- src/lib.rs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fddcca0a..806da7ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: include: - - rust: 1.56.0 # MSRV + - rust: 1.56.1 # MSRV features: - rust: stable features: serde @@ -57,7 +57,7 @@ jobs: strategy: matrix: include: - - rust: 1.56.0 + - rust: 1.56.1 target: thumbv6m-none-eabi - rust: stable target: thumbv6m-none-eabi diff --git a/Cargo.toml b/Cargo.toml index 85867a1b..75ebd6d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ license = "Apache-2.0 OR MIT" description = "A hash table with consistent order and fast iteration." keywords = ["hashmap", "no_std"] categories = ["data-structures", "no-std"] -rust-version = "1.56" +rust-version = "1.56.1" [lib] bench = false diff --git a/README.md b/README.md index 5dc3f181..de7ab337 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![build status](https://github.com/bluss/indexmap/workflows/Continuous%20integration/badge.svg?branch=master)](https://github.com/bluss/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.56%2B-orange.svg)](https://img.shields.io/badge/rust-1.56%2B-orange.svg) +[![rustc](https://img.shields.io/badge/rust-1.56.1%2B-orange.svg)](https://img.shields.io/badge/rust-1.56.1%2B-orange.svg) A pure-Rust hash table which preserves (in a limited sense) insertion order. diff --git a/RELEASES.md b/RELEASES.md index c1efa646..a0d9a77d 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,6 +1,6 @@ - 2.0.0 (pending) - - **MSRV**: Rust 1.56 or later is now required. + - **MSRV**: Rust 1.56.1 or later is now required. - The `hashbrown` dependency has been updated to version 0.12. diff --git a/src/lib.rs b/src/lib.rs index 3d796ea7..3b2c571c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,7 +53,7 @@ //! //! ### Rust Version //! -//! This version of indexmap requires Rust 1.56 or later. +//! This version of indexmap requires Rust 1.56.1 or later. //! //! The indexmap 2.x release series will use a carefully considered version //! upgrade policy, where in a later 2.x version, we will raise the minimum From 0937707fa11d08eb8610dbd8927476221c5b082c Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 6 May 2022 18:30:46 -0700 Subject: [PATCH 075/236] Use first-class patterns for split_first/last --- src/map/slice.rs | 8 ++++---- src/set/slice.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/map/slice.rs b/src/map/slice.rs index 07c6705a..4b2029db 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -130,7 +130,7 @@ impl Slice { /// Returns the first key-value pair and the rest of the slice, /// or `None` if it is empty. pub fn split_first(&self) -> Option<((&K, &V), &Self)> { - if let Some((first, rest)) = self.entries.split_first() { + if let [first, rest @ ..] = &self.entries { Some((first.refs(), Self::from_slice(rest))) } else { None @@ -140,7 +140,7 @@ impl Slice { /// Returns the first key-value pair and the rest of the slice, /// with mutable access to the value, or `None` if it is empty. pub fn split_first_mut(&mut self) -> Option<((&K, &mut V), &mut Self)> { - if let Some((first, rest)) = self.entries.split_first_mut() { + if let [first, rest @ ..] = &mut self.entries { Some((first.ref_mut(), Self::from_mut_slice(rest))) } else { None @@ -150,7 +150,7 @@ impl Slice { /// Returns the last key-value pair and the rest of the slice, /// or `None` if it is empty. pub fn split_last(&self) -> Option<((&K, &V), &Self)> { - if let Some((last, rest)) = self.entries.split_last() { + if let [rest @ .., last] = &self.entries { Some((last.refs(), Self::from_slice(rest))) } else { None @@ -160,7 +160,7 @@ impl Slice { /// Returns the last key-value pair and the rest of the slice, /// with mutable access to the value, or `None` if it is empty. pub fn split_last_mut(&mut self) -> Option<((&K, &mut V), &mut Self)> { - if let Some((last, rest)) = self.entries.split_last_mut() { + if let [rest @ .., last] = &mut self.entries { Some((last.ref_mut(), Self::from_mut_slice(rest))) } else { None diff --git a/src/set/slice.rs b/src/set/slice.rs index dd317ebd..0924e8b9 100644 --- a/src/set/slice.rs +++ b/src/set/slice.rs @@ -88,7 +88,7 @@ impl Slice { /// Returns the first value and the rest of the slice, /// or `None` if it is empty. pub fn split_first(&self) -> Option<(&T, &Self)> { - if let Some((first, rest)) = self.entries.split_first() { + if let [first, rest @ ..] = &self.entries { Some((&first.key, Self::from_slice(rest))) } else { None @@ -98,7 +98,7 @@ impl Slice { /// Returns the last value and the rest of the slice, /// or `None` if it is empty. pub fn split_last(&self) -> Option<(&T, &Self)> { - if let Some((last, rest)) = self.entries.split_last() { + if let [rest @ .., last] = &self.entries { Some((&last.key, Self::from_slice(rest))) } else { None From cda1a0b5c947330ece313fe3ab2cc1fe906f1ed4 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 6 May 2022 18:30:56 -0700 Subject: [PATCH 076/236] cargo fmt --- src/map/core.rs | 3 ++- src/serde_seq.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/map/core.rs b/src/map/core.rs index 50ebebea..0bc54621 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -204,7 +204,8 @@ impl IndexMapCore { /// Shrink the capacity of the map with a lower bound pub(crate) fn shrink_to(&mut self, min_capacity: usize) { - self.indices.shrink_to(min_capacity, get_hash(&self.entries)); + self.indices + .shrink_to(min_capacity, get_hash(&self.entries)); self.entries.shrink_to(min_capacity); } diff --git a/src/serde_seq.rs b/src/serde_seq.rs index b8bd8203..c3ab6623 100644 --- a/src/serde_seq.rs +++ b/src/serde_seq.rs @@ -27,9 +27,9 @@ use core::fmt::{self, Formatter}; use core::hash::{BuildHasher, Hash}; use core::marker::PhantomData; -use crate::IndexMap; use crate::map::Slice as MapSlice; use crate::set::Slice as SetSlice; +use crate::IndexMap; /// Serializes a `map::Slice` as an ordered sequence. /// From adb8c1012e40261bf87084f8df51871e47d0affc Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 6 May 2022 18:37:19 -0700 Subject: [PATCH 077/236] typo fix --- src/serde_seq.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/serde_seq.rs b/src/serde_seq.rs index c3ab6623..e8d4c43c 100644 --- a/src/serde_seq.rs +++ b/src/serde_seq.rs @@ -34,7 +34,7 @@ use crate::IndexMap; /// Serializes a `map::Slice` as an ordered sequence. /// /// This behaves like [`crate::serde_seq`] for `IndexMap`, serializing a sequence -/// of `(key, value)` pairs, rather than as a map that might not preserver order. +/// of `(key, value)` pairs, rather than as a map that might not preserve order. /// /// Requires crate feature `"serde"` or `"serde-1"` impl Serialize for MapSlice From ebe2f2993dec26ce634711f87ed70e8d2f9fc46c Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 6 May 2022 18:38:42 -0700 Subject: [PATCH 078/236] Remove outdated references to serde-1 --- src/serde_seq.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/serde_seq.rs b/src/serde_seq.rs index e8d4c43c..618e871f 100644 --- a/src/serde_seq.rs +++ b/src/serde_seq.rs @@ -36,7 +36,7 @@ use crate::IndexMap; /// This behaves like [`crate::serde_seq`] for `IndexMap`, serializing a sequence /// of `(key, value)` pairs, rather than as a map that might not preserve order. /// -/// Requires crate feature `"serde"` or `"serde-1"` +/// Requires crate feature `"serde"` impl Serialize for MapSlice where K: Serialize, @@ -52,7 +52,7 @@ where /// Serializes a `set::Slice` as an ordered sequence. /// -/// Requires crate feature `"serde"` or `"serde-1"` +/// Requires crate feature `"serde"` impl Serialize for SetSlice where T: Serialize, From 54a48d2dd1647befc760ab05d807639f79375d97 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sat, 27 Feb 2021 11:36:53 -0800 Subject: [PATCH 079/236] 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. --- src/map.rs | 13 +++++++++ src/map/core.rs | 72 ++++++++++++++++++++++++++++++++++++++++--------- src/set.rs | 13 +++++++++ tests/quick.rs | 39 +++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 12 deletions(-) diff --git a/src/map.rs b/src/map.rs index 93622bc6..a1647d25 100644 --- a/src/map.rs +++ b/src/map.rs @@ -876,6 +876,19 @@ impl IndexMap { 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. diff --git a/src/map/core.rs b/src/map/core.rs index 0bc54621..fca9c384 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -283,29 +283,77 @@ impl IndexMapCore { /// /// 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 + // Correct indices that point to the entries that followed the removed entry. + self.decrement_indices(index + 1, self.entries.len()); + + // Use Vec::remove to actually remove the entry. let entry = self.entries.remove(index); + (entry.key, entry.value) + } - // 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` + /// 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 diff --git a/src/set.rs b/src/set.rs index bdcbaffb..60c4ebdb 100644 --- a/src/set.rs +++ b/src/set.rs @@ -731,6 +731,19 @@ impl IndexSet { 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. diff --git a/tests/quick.rs b/tests/quick.rs index 4f462878..792aeb7a 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -216,6 +216,45 @@ quickcheck_limit! { map[&key] == value && map[i] == value }) } + + fn swap_indices(vec: Vec, a: usize, b: usize) -> TestResult { + let mut set = IndexSet::::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, from: usize, to: usize) -> TestResult { + let mut set = IndexSet::::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::*; From 6b425e4e216634a0475ada0e17a56fa7c050d63f Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 19 May 2022 15:38:42 -0700 Subject: [PATCH 080/236] Add an MSRV note for retain_mut --- src/map/core.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/map/core.rs b/src/map/core.rs index 0bc54621..b74e6f35 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -399,6 +399,7 @@ impl IndexMapCore { where F: FnMut(&mut K, &mut V) -> bool, { + // FIXME: This could use Vec::retain_mut with MSRV 1.61. // Like Vec::retain in self.entries, but with mutable K and V. // We swap-shift all the items we want to keep, truncate the rest, // then rebuild the raw hash table with the new indexes. From d1100674244fe629837481d4ef7844b74a896b13 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Mon, 23 May 2022 16:56:34 -0700 Subject: [PATCH 081/236] Use `u8` test indices so quickcheck is less likely to go out of bounds. --- tests/quick.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/quick.rs b/tests/quick.rs index 792aeb7a..e9d96acc 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -217,8 +217,12 @@ quickcheck_limit! { }) } - fn swap_indices(vec: Vec, a: usize, b: usize) -> TestResult { + // Use `u8` test indices so quickcheck is less likely to go out of bounds. + fn 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); + if a >= set.len() || b >= set.len() { return TestResult::discard(); } @@ -236,8 +240,12 @@ quickcheck_limit! { TestResult::passed() } - fn move_index(vec: Vec, from: usize, to: usize) -> TestResult { + // Use `u8` test indices so quickcheck is less likely to go out of bounds. + fn 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); + if from >= set.len() || to >= set.len() { return TestResult::discard(); } From 3848768aedcf3e91d2cbf73e0099e6880cac8e7c Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Mon, 23 May 2022 17:30:44 -0700 Subject: [PATCH 082/236] Add a release note for move_index --- RELEASES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index a0d9a77d..c818f3bc 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -19,6 +19,10 @@ - The new `IndexMap::shrink_to` and `IndexSet::shrink_to` methods shrink the capacity with a lower bound. + - The new `IndexMap::move_index` and `IndexSet::move_index` methods change + the position of an item from one index to another, shifting the items + between to accommodate the move. + - The new `map::Slice` and `set::Slice` offer a linear view of maps and sets, behaving a lot like normal `[(K, V)]` and `[T]` slices. Notably, comparison traits like `Eq` only consider items in order, rather than hash From 8e7691d5aaabf5c82d2665eb0dbb6a746090da96 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 27 May 2022 16:46:18 -0700 Subject: [PATCH 083/236] Update rustc-rayon to 0.4 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 75ebd6d8..8864b680 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ rayon = { version = "1.4.1", optional = true } # Internal feature, only used when building as part of rustc, # not part of the stable interface of this crate. -rustc-rayon = { version = "0.3", optional = true } +rustc-rayon = { version = "0.4", optional = true } [dependencies.hashbrown] version = "0.12" From e49cdbc58a9493a32605f5e646d26fea1db83a13 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Mon, 6 Jun 2022 12:05:29 -0700 Subject: [PATCH 084/236] Remove internal patterns in `indexmap!` and `indexset!` --- src/macros.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index 20ab58db..889dbe7e 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -19,18 +19,17 @@ /// assert_eq!(map.keys().next(), Some(&"a")); /// ``` macro_rules! indexmap { - (@single $($x:tt)*) => (()); - (@count $($rest:expr),*) => (<[()]>::len(&[$($crate::indexmap!(@single $rest)),*])); - ($($key:expr => $value:expr,)+) => { $crate::indexmap!($($key => $value),+) }; ($($key:expr => $value:expr),*) => { { - let _cap = $crate::indexmap!(@count $($key),*); - let mut _map = $crate::IndexMap::with_capacity(_cap); + // Note: `stringify!($key)` is just here to consume the repetition, + // but we throw away that string literal during constant evaluation. + const CAP: usize = <[()]>::len(&[$({ stringify!($key); }),*]); + let mut map = $crate::IndexMap::with_capacity(CAP); $( - _map.insert($key, $value); + map.insert($key, $value); )* - _map + map } }; } @@ -56,18 +55,17 @@ macro_rules! indexmap { /// assert_eq!(set.iter().next(), Some(&"a")); /// ``` macro_rules! indexset { - (@single $($x:tt)*) => (()); - (@count $($rest:expr),*) => (<[()]>::len(&[$($crate::indexset!(@single $rest)),*])); - ($($value:expr,)+) => { $crate::indexset!($($value),+) }; ($($value:expr),*) => { { - let _cap = $crate::indexset!(@count $($value),*); - let mut _set = $crate::IndexSet::with_capacity(_cap); + // Note: `stringify!($value)` is just here to consume the repetition, + // but we throw away that string literal during constant evaluation. + const CAP: usize = <[()]>::len(&[$({ stringify!($value); }),*]); + let mut set = $crate::IndexSet::with_capacity(CAP); $( - _set.insert($value); + set.insert($value); )* - _set + set } }; } From 2f731f161d567bb199795596e54ee687f4285e4e Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 16 Jun 2022 17:33:40 -0700 Subject: [PATCH 085/236] Sync release log from 1.9.0 --- RELEASES.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index c818f3bc..48b50b5d 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,9 +1,5 @@ - 2.0.0 (pending) - - **MSRV**: Rust 1.56.1 or later is now required. - - - The `hashbrown` dependency has been updated to version 0.12. - - The `"std"` feature is no longer auto-detected. It is included in the default feature set, or else can be enabled like any other Cargo feature. @@ -14,6 +10,17 @@ the key part from `&mut K` to `&K`. There is also a new alternative `MutableKeys::get_index_mut2` to access the former behavior. + - The new `map::Slice` and `set::Slice` offer a linear view of maps + and sets, behaving a lot like normal `[(K, V)]` and `[T]` slices. Notably, + comparison traits like `Eq` only consider items in order, rather than hash + lookups, and slices even implement `Hash`. + +- 1.9.0 + + - **MSRV**: Rust 1.56.1 or later is now required. + + - The `hashbrown` dependency has been updated to version 0.12. + - `IterMut` and `ValuesMut` now implement `Debug`. - The new `IndexMap::shrink_to` and `IndexSet::shrink_to` methods shrink @@ -23,10 +30,9 @@ the position of an item from one index to another, shifting the items between to accommodate the move. - - The new `map::Slice` and `set::Slice` offer a linear view of maps - and sets, behaving a lot like normal `[(K, V)]` and `[T]` slices. Notably, - comparison traits like `Eq` only consider items in order, rather than hash - lookups, and slices even implement `Hash`. +- 1.8.2 + + - Bump the `rustc-rayon` dependency, for compiler use only. - 1.8.1 From 97c4efe9d0bf19b3254d09071b085086053fdcb7 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 16 Jun 2022 18:09:35 -0700 Subject: [PATCH 086/236] Fix the RELEASES.md link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index de7ab337..a1167985 100644 --- a/README.md +++ b/README.md @@ -52,4 +52,4 @@ which is roughly: # Recent Changes -See [RELEASES.md](https://github.com/bluss/indexmap/blob/master/README.md). +See [RELEASES.md](https://github.com/bluss/indexmap/blob/master/RELEASES.md). From f6487e7e042b39472f4f5075e7045aa2dbabb5b5 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Thu, 16 Jun 2022 23:11:27 -0700 Subject: [PATCH 087/236] Revert "Bump MSRV to 1.56.1, matching hashbrown as of 0.12.1" This reverts commit 8a571c6d68cb38c283d563ff6972613e0eea4111. --- .github/workflows/ci.yml | 4 ++-- Cargo.toml | 2 +- README.md | 2 +- src/lib.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 806da7ec..fddcca0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: include: - - rust: 1.56.1 # MSRV + - rust: 1.56.0 # MSRV features: - rust: stable features: serde @@ -57,7 +57,7 @@ jobs: strategy: matrix: include: - - rust: 1.56.1 + - rust: 1.56.0 target: thumbv6m-none-eabi - rust: stable target: thumbv6m-none-eabi diff --git a/Cargo.toml b/Cargo.toml index 8864b680..2037f0b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ license = "Apache-2.0 OR MIT" description = "A hash table with consistent order and fast iteration." keywords = ["hashmap", "no_std"] categories = ["data-structures", "no-std"] -rust-version = "1.56.1" +rust-version = "1.56" [lib] bench = false diff --git a/README.md b/README.md index a1167985..d80b7099 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![build status](https://github.com/bluss/indexmap/workflows/Continuous%20integration/badge.svg?branch=master)](https://github.com/bluss/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.56.1%2B-orange.svg)](https://img.shields.io/badge/rust-1.56.1%2B-orange.svg) +[![rustc](https://img.shields.io/badge/rust-1.56%2B-orange.svg)](https://img.shields.io/badge/rust-1.56%2B-orange.svg) A pure-Rust hash table which preserves (in a limited sense) insertion order. diff --git a/src/lib.rs b/src/lib.rs index 3b2c571c..3d796ea7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,7 +53,7 @@ //! //! ### Rust Version //! -//! This version of indexmap requires Rust 1.56.1 or later. +//! This version of indexmap requires Rust 1.56 or later. //! //! The indexmap 2.x release series will use a carefully considered version //! upgrade policy, where in a later 2.x version, we will raise the minimum From b6aa495bede3ae3ed3e1303d35646e9ab719378c Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Thu, 16 Jun 2022 23:15:27 -0700 Subject: [PATCH 088/236] Run CI on 1.56.1 until next hashbrown release The PR changing hashbrown's `rust-version` from 1.56.1 to 1.56.0 has been merged to master but not made it into a hashbrown release yet. --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fddcca0a..806da7ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: include: - - rust: 1.56.0 # MSRV + - rust: 1.56.1 # MSRV features: - rust: stable features: serde @@ -57,7 +57,7 @@ jobs: strategy: matrix: include: - - rust: 1.56.0 + - rust: 1.56.1 target: thumbv6m-none-eabi - rust: stable target: thumbv6m-none-eabi From b283dd7792f9338aeb182e21f29a80cb17ba4173 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 21 Jun 2022 10:37:14 -0700 Subject: [PATCH 089/236] Release 1.9.1 (cherry picked from commit 3f6cdde72a11d0ea6ae8787346a725e762886c18) --- RELEASES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index 48b50b5d..651f8b4e 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -15,6 +15,12 @@ comparison traits like `Eq` only consider items in order, rather than hash lookups, and slices even implement `Hash`. +- 1.9.1 + + - The MSRV now allows Rust 1.56.0 as well. However, currently `hashbrown` + 0.12.1 requires 1.56.1, so users on 1.56.0 should downgrade that to 0.12.0 + until there is a later published version relaxing its requirement. + - 1.9.0 - **MSRV**: Rust 1.56.1 or later is now required. From 68f4f03ea5c4b2f2f4cd61a9aeb27576d81e6a1d Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 12 Jul 2022 14:06:05 -0700 Subject: [PATCH 090/236] Move map::tests to its own file --- src/map.rs | 427 +---------------------------------------------- src/map/tests.rs | 420 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 423 insertions(+), 424 deletions(-) create mode 100644 src/map/tests.rs diff --git a/src/map.rs b/src/map.rs index a1647d25..2bd5b21f 100644 --- a/src/map.rs +++ b/src/map.rs @@ -4,6 +4,9 @@ mod core; mod slice; +#[cfg(test)] +mod tests; + pub use self::slice::Slice; pub use crate::mutable_keys::MutableKeys; @@ -1568,427 +1571,3 @@ where S: BuildHasher, { } - -#[cfg(test)] -mod tests { - use super::*; - use std::string::String; - - #[test] - fn it_works() { - let mut map = IndexMap::new(); - assert_eq!(map.is_empty(), true); - map.insert(1, ()); - map.insert(1, ()); - assert_eq!(map.len(), 1); - assert!(map.get(&1).is_some()); - assert_eq!(map.is_empty(), false); - } - - #[test] - fn new() { - let map = IndexMap::::new(); - println!("{:?}", map); - assert_eq!(map.capacity(), 0); - assert_eq!(map.len(), 0); - assert_eq!(map.is_empty(), true); - } - - #[test] - fn insert() { - let insert = [0, 4, 2, 12, 8, 7, 11, 5]; - let not_present = [1, 3, 6, 9, 10]; - let mut map = IndexMap::with_capacity(insert.len()); - - for (i, &elt) in insert.iter().enumerate() { - assert_eq!(map.len(), i); - map.insert(elt, elt); - assert_eq!(map.len(), i + 1); - assert_eq!(map.get(&elt), Some(&elt)); - assert_eq!(map[&elt], elt); - } - println!("{:?}", map); - - for &elt in ¬_present { - assert!(map.get(&elt).is_none()); - } - } - - #[test] - fn insert_full() { - let insert = vec![9, 2, 7, 1, 4, 6, 13]; - let present = vec![1, 6, 2]; - let mut map = IndexMap::with_capacity(insert.len()); - - for (i, &elt) in insert.iter().enumerate() { - assert_eq!(map.len(), i); - let (index, existing) = map.insert_full(elt, elt); - assert_eq!(existing, None); - assert_eq!(Some(index), map.get_full(&elt).map(|x| x.0)); - assert_eq!(map.len(), i + 1); - } - - let len = map.len(); - for &elt in &present { - let (index, existing) = map.insert_full(elt, elt); - assert_eq!(existing, Some(elt)); - assert_eq!(Some(index), map.get_full(&elt).map(|x| x.0)); - assert_eq!(map.len(), len); - } - } - - #[test] - fn insert_2() { - let mut map = IndexMap::with_capacity(16); - - let mut keys = vec![]; - keys.extend(0..16); - keys.extend(if cfg!(miri) { 32..64 } else { 128..267 }); - - for &i in &keys { - let old_map = map.clone(); - map.insert(i, ()); - for key in old_map.keys() { - if map.get(key).is_none() { - println!("old_map: {:?}", old_map); - println!("map: {:?}", map); - panic!("did not find {} in map", key); - } - } - } - - for &i in &keys { - assert!(map.get(&i).is_some(), "did not find {}", i); - } - } - - #[test] - fn insert_order() { - 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.insert(elt, ()); - } - - assert_eq!(map.keys().count(), map.len()); - assert_eq!(map.keys().count(), insert.len()); - for (a, b) in insert.iter().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]; - let not_present = [1, 3, 6, 9, 10]; - let mut map = IndexMap::with_capacity(insert.len()); - - for (i, &elt) in insert.iter().enumerate() { - assert_eq!(map.len(), i); - map.insert(elt, elt); - assert_eq!(map.len(), i + 1); - assert_eq!(map.get(&elt), Some(&elt)); - assert_eq!(map[&elt], elt); - } - - println!("{:?}", map); - for &elt in &insert { - map.insert(elt * 10, elt); - } - for &elt in &insert { - map.insert(elt * 100, elt); - } - for (i, &elt) in insert.iter().cycle().enumerate().take(100) { - map.insert(elt * 100 + i as i32, elt); - } - println!("{:?}", map); - for &elt in ¬_present { - assert!(map.get(&elt).is_none()); - } - } - - #[test] - fn reserve() { - let mut map = IndexMap::::new(); - assert_eq!(map.capacity(), 0); - map.reserve(100); - let capacity = map.capacity(); - assert!(capacity >= 100); - for i in 0..capacity { - assert_eq!(map.len(), i); - map.insert(i, i * i); - assert_eq!(map.len(), i + 1); - assert_eq!(map.capacity(), capacity); - assert_eq!(map.get(&i), Some(&(i * i))); - } - map.insert(capacity, std::usize::MAX); - assert_eq!(map.len(), capacity + 1); - assert!(map.capacity() > capacity); - assert_eq!(map.get(&capacity), Some(&std::usize::MAX)); - } - - #[test] - fn shrink_to_fit() { - let mut map = IndexMap::::new(); - assert_eq!(map.capacity(), 0); - for i in 0..100 { - assert_eq!(map.len(), i); - map.insert(i, i * i); - assert_eq!(map.len(), i + 1); - assert!(map.capacity() >= i + 1); - assert_eq!(map.get(&i), Some(&(i * i))); - map.shrink_to_fit(); - assert_eq!(map.len(), i + 1); - assert_eq!(map.capacity(), i + 1); - assert_eq!(map.get(&i), Some(&(i * i))); - } - } - - #[test] - fn remove() { - 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.insert(elt, elt); - } - - assert_eq!(map.keys().count(), map.len()); - assert_eq!(map.keys().count(), insert.len()); - for (a, b) in insert.iter().zip(map.keys()) { - assert_eq!(a, b); - } - - let remove_fail = [99, 77]; - let remove = [4, 12, 8, 7]; - - for &key in &remove_fail { - assert!(map.swap_remove_full(&key).is_none()); - } - println!("{:?}", map); - for &key in &remove { - //println!("{:?}", map); - let index = map.get_full(&key).unwrap().0; - assert_eq!(map.swap_remove_full(&key), Some((index, key, key))); - } - println!("{:?}", map); - - for key in &insert { - assert_eq!(map.get(key).is_some(), !remove.contains(key)); - } - assert_eq!(map.len(), insert.len() - remove.len()); - assert_eq!(map.keys().count(), insert.len() - remove.len()); - } - - #[test] - fn remove_to_empty() { - let mut map = indexmap! { 0 => 0, 4 => 4, 5 => 5 }; - map.swap_remove(&5).unwrap(); - map.swap_remove(&4).unwrap(); - map.swap_remove(&0).unwrap(); - assert!(map.is_empty()); - } - - #[test] - fn swap_remove_index() { - 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.insert(elt, elt * 2); - } - - let mut vector = insert.to_vec(); - let remove_sequence = &[3, 3, 10, 4, 5, 4, 3, 0, 1]; - - // check that the same swap remove sequence on vec and map - // have the same result. - for &rm in remove_sequence { - let out_vec = vector.swap_remove(rm); - let (out_map, _) = map.swap_remove_index(rm).unwrap(); - assert_eq!(out_vec, out_map); - } - assert_eq!(vector.len(), map.len()); - for (a, b) in vector.iter().zip(map.keys()) { - assert_eq!(a, b); - } - } - - #[test] - fn partial_eq_and_eq() { - let mut map_a = IndexMap::new(); - map_a.insert(1, "1"); - map_a.insert(2, "2"); - let mut map_b = map_a.clone(); - assert_eq!(map_a, map_b); - map_b.swap_remove(&1); - assert_ne!(map_a, map_b); - - let map_c: IndexMap<_, String> = map_b.into_iter().map(|(k, v)| (k, v.into())).collect(); - assert_ne!(map_a, map_c); - assert_ne!(map_c, map_a); - } - - #[test] - fn extend() { - let mut map = IndexMap::new(); - map.extend(vec![(&1, &2), (&3, &4)]); - map.extend(vec![(5, 6)]); - assert_eq!( - map.into_iter().collect::>(), - vec![(1, 2), (3, 4), (5, 6)] - ); - } - - #[test] - fn entry() { - let mut map = IndexMap::new(); - - map.insert(1, "1"); - map.insert(2, "2"); - { - let e = map.entry(3); - assert_eq!(e.index(), 2); - let e = e.or_insert("3"); - assert_eq!(e, &"3"); - } - - let e = map.entry(2); - assert_eq!(e.index(), 1); - assert_eq!(e.key(), &2); - match e { - Entry::Occupied(ref e) => assert_eq!(e.get(), &"2"), - Entry::Vacant(_) => panic!(), - } - assert_eq!(e.or_insert("4"), &"2"); - } - - #[test] - fn entry_and_modify() { - let mut map = IndexMap::new(); - - map.insert(1, "1"); - map.entry(1).and_modify(|x| *x = "2"); - assert_eq!(Some(&"2"), map.get(&1)); - - map.entry(2).and_modify(|x| *x = "doesn't exist"); - assert_eq!(None, map.get(&2)); - } - - #[test] - fn entry_or_default() { - let mut map = IndexMap::new(); - - #[derive(Debug, PartialEq)] - enum TestEnum { - DefaultValue, - NonDefaultValue, - } - - impl Default for TestEnum { - fn default() -> Self { - TestEnum::DefaultValue - } - } - - map.insert(1, TestEnum::NonDefaultValue); - assert_eq!(&mut TestEnum::NonDefaultValue, map.entry(1).or_default()); - - assert_eq!(&mut TestEnum::DefaultValue, map.entry(2).or_default()); - } - - #[test] - fn occupied_entry_key() { - // These keys match hash and equality, but their addresses are distinct. - let (k1, k2) = (&mut 1, &mut 1); - let k1_ptr = k1 as *const i32; - let k2_ptr = k2 as *const i32; - assert_ne!(k1_ptr, k2_ptr); - - let mut map = IndexMap::new(); - map.insert(k1, "value"); - match map.entry(k2) { - Entry::Occupied(ref e) => { - // `OccupiedEntry::key` should reference the key in the map, - // not the key that was used to find the entry. - let ptr = *e.key() as *const i32; - assert_eq!(ptr, k1_ptr); - assert_ne!(ptr, k2_ptr); - } - Entry::Vacant(_) => panic!(), - } - } - - #[test] - fn keys() { - let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; - let map: IndexMap<_, _> = vec.into_iter().collect(); - let keys: Vec<_> = map.keys().copied().collect(); - assert_eq!(keys.len(), 3); - assert!(keys.contains(&1)); - assert!(keys.contains(&2)); - assert!(keys.contains(&3)); - } - - #[test] - fn into_keys() { - let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; - let map: IndexMap<_, _> = vec.into_iter().collect(); - let keys: Vec = map.into_keys().collect(); - assert_eq!(keys.len(), 3); - assert!(keys.contains(&1)); - assert!(keys.contains(&2)); - assert!(keys.contains(&3)); - } - - #[test] - fn values() { - let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; - let map: IndexMap<_, _> = vec.into_iter().collect(); - let values: Vec<_> = map.values().copied().collect(); - assert_eq!(values.len(), 3); - assert!(values.contains(&'a')); - assert!(values.contains(&'b')); - assert!(values.contains(&'c')); - } - - #[test] - fn values_mut() { - let vec = vec![(1, 1), (2, 2), (3, 3)]; - let mut map: IndexMap<_, _> = vec.into_iter().collect(); - for value in map.values_mut() { - *value *= 2 - } - let values: Vec<_> = map.values().copied().collect(); - assert_eq!(values.len(), 3); - assert!(values.contains(&2)); - assert!(values.contains(&4)); - assert!(values.contains(&6)); - } - - #[test] - fn into_values() { - let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; - let map: IndexMap<_, _> = vec.into_iter().collect(); - let values: Vec = map.into_values().collect(); - assert_eq!(values.len(), 3); - assert!(values.contains(&'a')); - assert!(values.contains(&'b')); - assert!(values.contains(&'c')); - } - - #[test] - #[cfg(feature = "std")] - fn from_array() { - let map = IndexMap::from([(1, 2), (3, 4)]); - let mut expected = IndexMap::new(); - expected.insert(1, 2); - expected.insert(3, 4); - - assert_eq!(map, expected) - } -} diff --git a/src/map/tests.rs b/src/map/tests.rs new file mode 100644 index 00000000..b6c6a42d --- /dev/null +++ b/src/map/tests.rs @@ -0,0 +1,420 @@ +use super::*; +use std::string::String; + +#[test] +fn it_works() { + let mut map = IndexMap::new(); + assert_eq!(map.is_empty(), true); + map.insert(1, ()); + map.insert(1, ()); + assert_eq!(map.len(), 1); + assert!(map.get(&1).is_some()); + assert_eq!(map.is_empty(), false); +} + +#[test] +fn new() { + let map = IndexMap::::new(); + println!("{:?}", map); + assert_eq!(map.capacity(), 0); + assert_eq!(map.len(), 0); + assert_eq!(map.is_empty(), true); +} + +#[test] +fn insert() { + let insert = [0, 4, 2, 12, 8, 7, 11, 5]; + let not_present = [1, 3, 6, 9, 10]; + let mut map = IndexMap::with_capacity(insert.len()); + + for (i, &elt) in insert.iter().enumerate() { + assert_eq!(map.len(), i); + map.insert(elt, elt); + assert_eq!(map.len(), i + 1); + assert_eq!(map.get(&elt), Some(&elt)); + assert_eq!(map[&elt], elt); + } + println!("{:?}", map); + + for &elt in ¬_present { + assert!(map.get(&elt).is_none()); + } +} + +#[test] +fn insert_full() { + let insert = vec![9, 2, 7, 1, 4, 6, 13]; + let present = vec![1, 6, 2]; + let mut map = IndexMap::with_capacity(insert.len()); + + for (i, &elt) in insert.iter().enumerate() { + assert_eq!(map.len(), i); + let (index, existing) = map.insert_full(elt, elt); + assert_eq!(existing, None); + assert_eq!(Some(index), map.get_full(&elt).map(|x| x.0)); + assert_eq!(map.len(), i + 1); + } + + let len = map.len(); + for &elt in &present { + let (index, existing) = map.insert_full(elt, elt); + assert_eq!(existing, Some(elt)); + assert_eq!(Some(index), map.get_full(&elt).map(|x| x.0)); + assert_eq!(map.len(), len); + } +} + +#[test] +fn insert_2() { + let mut map = IndexMap::with_capacity(16); + + let mut keys = vec![]; + keys.extend(0..16); + keys.extend(if cfg!(miri) { 32..64 } else { 128..267 }); + + for &i in &keys { + let old_map = map.clone(); + map.insert(i, ()); + for key in old_map.keys() { + if map.get(key).is_none() { + println!("old_map: {:?}", old_map); + println!("map: {:?}", map); + panic!("did not find {} in map", key); + } + } + } + + for &i in &keys { + assert!(map.get(&i).is_some(), "did not find {}", i); + } +} + +#[test] +fn insert_order() { + 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.insert(elt, ()); + } + + assert_eq!(map.keys().count(), map.len()); + assert_eq!(map.keys().count(), insert.len()); + for (a, b) in insert.iter().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]; + let not_present = [1, 3, 6, 9, 10]; + let mut map = IndexMap::with_capacity(insert.len()); + + for (i, &elt) in insert.iter().enumerate() { + assert_eq!(map.len(), i); + map.insert(elt, elt); + assert_eq!(map.len(), i + 1); + assert_eq!(map.get(&elt), Some(&elt)); + assert_eq!(map[&elt], elt); + } + + println!("{:?}", map); + for &elt in &insert { + map.insert(elt * 10, elt); + } + for &elt in &insert { + map.insert(elt * 100, elt); + } + for (i, &elt) in insert.iter().cycle().enumerate().take(100) { + map.insert(elt * 100 + i as i32, elt); + } + println!("{:?}", map); + for &elt in ¬_present { + assert!(map.get(&elt).is_none()); + } +} + +#[test] +fn reserve() { + let mut map = IndexMap::::new(); + assert_eq!(map.capacity(), 0); + map.reserve(100); + let capacity = map.capacity(); + assert!(capacity >= 100); + for i in 0..capacity { + assert_eq!(map.len(), i); + map.insert(i, i * i); + assert_eq!(map.len(), i + 1); + assert_eq!(map.capacity(), capacity); + assert_eq!(map.get(&i), Some(&(i * i))); + } + map.insert(capacity, std::usize::MAX); + assert_eq!(map.len(), capacity + 1); + assert!(map.capacity() > capacity); + assert_eq!(map.get(&capacity), Some(&std::usize::MAX)); +} + +#[test] +fn shrink_to_fit() { + let mut map = IndexMap::::new(); + assert_eq!(map.capacity(), 0); + for i in 0..100 { + assert_eq!(map.len(), i); + map.insert(i, i * i); + assert_eq!(map.len(), i + 1); + assert!(map.capacity() >= i + 1); + assert_eq!(map.get(&i), Some(&(i * i))); + map.shrink_to_fit(); + assert_eq!(map.len(), i + 1); + assert_eq!(map.capacity(), i + 1); + assert_eq!(map.get(&i), Some(&(i * i))); + } +} + +#[test] +fn remove() { + 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.insert(elt, elt); + } + + assert_eq!(map.keys().count(), map.len()); + assert_eq!(map.keys().count(), insert.len()); + for (a, b) in insert.iter().zip(map.keys()) { + assert_eq!(a, b); + } + + let remove_fail = [99, 77]; + let remove = [4, 12, 8, 7]; + + for &key in &remove_fail { + assert!(map.swap_remove_full(&key).is_none()); + } + println!("{:?}", map); + for &key in &remove { + //println!("{:?}", map); + let index = map.get_full(&key).unwrap().0; + assert_eq!(map.swap_remove_full(&key), Some((index, key, key))); + } + println!("{:?}", map); + + for key in &insert { + assert_eq!(map.get(key).is_some(), !remove.contains(key)); + } + assert_eq!(map.len(), insert.len() - remove.len()); + assert_eq!(map.keys().count(), insert.len() - remove.len()); +} + +#[test] +fn remove_to_empty() { + let mut map = indexmap! { 0 => 0, 4 => 4, 5 => 5 }; + map.swap_remove(&5).unwrap(); + map.swap_remove(&4).unwrap(); + map.swap_remove(&0).unwrap(); + assert!(map.is_empty()); +} + +#[test] +fn swap_remove_index() { + 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.insert(elt, elt * 2); + } + + let mut vector = insert.to_vec(); + let remove_sequence = &[3, 3, 10, 4, 5, 4, 3, 0, 1]; + + // check that the same swap remove sequence on vec and map + // have the same result. + for &rm in remove_sequence { + let out_vec = vector.swap_remove(rm); + let (out_map, _) = map.swap_remove_index(rm).unwrap(); + assert_eq!(out_vec, out_map); + } + assert_eq!(vector.len(), map.len()); + for (a, b) in vector.iter().zip(map.keys()) { + assert_eq!(a, b); + } +} + +#[test] +fn partial_eq_and_eq() { + let mut map_a = IndexMap::new(); + map_a.insert(1, "1"); + map_a.insert(2, "2"); + let mut map_b = map_a.clone(); + assert_eq!(map_a, map_b); + map_b.swap_remove(&1); + assert_ne!(map_a, map_b); + + let map_c: IndexMap<_, String> = map_b.into_iter().map(|(k, v)| (k, v.into())).collect(); + assert_ne!(map_a, map_c); + assert_ne!(map_c, map_a); +} + +#[test] +fn extend() { + let mut map = IndexMap::new(); + map.extend(vec![(&1, &2), (&3, &4)]); + map.extend(vec![(5, 6)]); + assert_eq!( + map.into_iter().collect::>(), + vec![(1, 2), (3, 4), (5, 6)] + ); +} + +#[test] +fn entry() { + let mut map = IndexMap::new(); + + map.insert(1, "1"); + map.insert(2, "2"); + { + let e = map.entry(3); + assert_eq!(e.index(), 2); + let e = e.or_insert("3"); + assert_eq!(e, &"3"); + } + + let e = map.entry(2); + assert_eq!(e.index(), 1); + assert_eq!(e.key(), &2); + match e { + Entry::Occupied(ref e) => assert_eq!(e.get(), &"2"), + Entry::Vacant(_) => panic!(), + } + assert_eq!(e.or_insert("4"), &"2"); +} + +#[test] +fn entry_and_modify() { + let mut map = IndexMap::new(); + + map.insert(1, "1"); + map.entry(1).and_modify(|x| *x = "2"); + assert_eq!(Some(&"2"), map.get(&1)); + + map.entry(2).and_modify(|x| *x = "doesn't exist"); + assert_eq!(None, map.get(&2)); +} + +#[test] +fn entry_or_default() { + let mut map = IndexMap::new(); + + #[derive(Debug, PartialEq)] + enum TestEnum { + DefaultValue, + NonDefaultValue, + } + + impl Default for TestEnum { + fn default() -> Self { + TestEnum::DefaultValue + } + } + + map.insert(1, TestEnum::NonDefaultValue); + assert_eq!(&mut TestEnum::NonDefaultValue, map.entry(1).or_default()); + + assert_eq!(&mut TestEnum::DefaultValue, map.entry(2).or_default()); +} + +#[test] +fn occupied_entry_key() { + // These keys match hash and equality, but their addresses are distinct. + let (k1, k2) = (&mut 1, &mut 1); + let k1_ptr = k1 as *const i32; + let k2_ptr = k2 as *const i32; + assert_ne!(k1_ptr, k2_ptr); + + let mut map = IndexMap::new(); + map.insert(k1, "value"); + match map.entry(k2) { + Entry::Occupied(ref e) => { + // `OccupiedEntry::key` should reference the key in the map, + // not the key that was used to find the entry. + let ptr = *e.key() as *const i32; + assert_eq!(ptr, k1_ptr); + assert_ne!(ptr, k2_ptr); + } + Entry::Vacant(_) => panic!(), + } +} + +#[test] +fn keys() { + let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; + let map: IndexMap<_, _> = vec.into_iter().collect(); + let keys: Vec<_> = map.keys().copied().collect(); + assert_eq!(keys.len(), 3); + assert!(keys.contains(&1)); + assert!(keys.contains(&2)); + assert!(keys.contains(&3)); +} + +#[test] +fn into_keys() { + let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; + let map: IndexMap<_, _> = vec.into_iter().collect(); + let keys: Vec = map.into_keys().collect(); + assert_eq!(keys.len(), 3); + assert!(keys.contains(&1)); + assert!(keys.contains(&2)); + assert!(keys.contains(&3)); +} + +#[test] +fn values() { + let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; + let map: IndexMap<_, _> = vec.into_iter().collect(); + let values: Vec<_> = map.values().copied().collect(); + assert_eq!(values.len(), 3); + assert!(values.contains(&'a')); + assert!(values.contains(&'b')); + assert!(values.contains(&'c')); +} + +#[test] +fn values_mut() { + let vec = vec![(1, 1), (2, 2), (3, 3)]; + let mut map: IndexMap<_, _> = vec.into_iter().collect(); + for value in map.values_mut() { + *value *= 2 + } + let values: Vec<_> = map.values().copied().collect(); + assert_eq!(values.len(), 3); + assert!(values.contains(&2)); + assert!(values.contains(&4)); + assert!(values.contains(&6)); +} + +#[test] +fn into_values() { + let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; + let map: IndexMap<_, _> = vec.into_iter().collect(); + let values: Vec = map.into_values().collect(); + assert_eq!(values.len(), 3); + assert!(values.contains(&'a')); + assert!(values.contains(&'b')); + assert!(values.contains(&'c')); +} + +#[test] +#[cfg(feature = "std")] +fn from_array() { + let map = IndexMap::from([(1, 2), (3, 4)]); + let mut expected = IndexMap::new(); + expected.insert(1, 2); + expected.insert(3, 4); + + assert_eq!(map, expected) +} From 63c0229dfce320afe3b6a13255d13aeeb9b5014a Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 12 Jul 2022 14:06:40 -0700 Subject: [PATCH 091/236] Move set::tests to its own file --- src/set.rs | 530 +---------------------------------------------- src/set/tests.rs | 523 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 526 insertions(+), 527 deletions(-) create mode 100644 src/set/tests.rs diff --git a/src/set.rs b/src/set.rs index 60c4ebdb..4ef2a9b8 100644 --- a/src/set.rs +++ b/src/set.rs @@ -2,6 +2,9 @@ mod slice; +#[cfg(test)] +mod tests; + pub use self::slice::Slice; #[cfg(feature = "rayon")] @@ -1421,530 +1424,3 @@ where self.difference(other).cloned().collect() } } - -#[cfg(test)] -mod tests { - use super::*; - use std::string::String; - - #[test] - fn it_works() { - let mut set = IndexSet::new(); - assert_eq!(set.is_empty(), true); - set.insert(1); - set.insert(1); - assert_eq!(set.len(), 1); - assert!(set.get(&1).is_some()); - assert_eq!(set.is_empty(), false); - } - - #[test] - fn new() { - let set = IndexSet::::new(); - println!("{:?}", set); - assert_eq!(set.capacity(), 0); - assert_eq!(set.len(), 0); - assert_eq!(set.is_empty(), true); - } - - #[test] - fn insert() { - let insert = [0, 4, 2, 12, 8, 7, 11, 5]; - let not_present = [1, 3, 6, 9, 10]; - let mut set = IndexSet::with_capacity(insert.len()); - - for (i, &elt) in insert.iter().enumerate() { - assert_eq!(set.len(), i); - set.insert(elt); - assert_eq!(set.len(), i + 1); - assert_eq!(set.get(&elt), Some(&elt)); - } - println!("{:?}", set); - - for &elt in ¬_present { - assert!(set.get(&elt).is_none()); - } - } - - #[test] - fn insert_full() { - let insert = vec![9, 2, 7, 1, 4, 6, 13]; - let present = vec![1, 6, 2]; - let mut set = IndexSet::with_capacity(insert.len()); - - for (i, &elt) in insert.iter().enumerate() { - assert_eq!(set.len(), i); - let (index, success) = set.insert_full(elt); - assert!(success); - assert_eq!(Some(index), set.get_full(&elt).map(|x| x.0)); - assert_eq!(set.len(), i + 1); - } - - let len = set.len(); - for &elt in &present { - let (index, success) = set.insert_full(elt); - assert!(!success); - assert_eq!(Some(index), set.get_full(&elt).map(|x| x.0)); - assert_eq!(set.len(), len); - } - } - - #[test] - fn insert_2() { - let mut set = IndexSet::with_capacity(16); - - let mut values = vec![]; - values.extend(0..16); - values.extend(if cfg!(miri) { 32..64 } else { 128..267 }); - - for &i in &values { - let old_set = set.clone(); - set.insert(i); - for value in old_set.iter() { - if set.get(value).is_none() { - println!("old_set: {:?}", old_set); - println!("set: {:?}", set); - panic!("did not find {} in set", value); - } - } - } - - for &i in &values { - assert!(set.get(&i).is_some(), "did not find {}", i); - } - } - - #[test] - fn insert_dup() { - let mut elements = vec![0, 2, 4, 6, 8]; - let mut set: IndexSet = elements.drain(..).collect(); - { - let (i, v) = set.get_full(&0).unwrap(); - assert_eq!(set.len(), 5); - assert_eq!(i, 0); - assert_eq!(*v, 0); - } - { - let inserted = set.insert(0); - let (i, v) = set.get_full(&0).unwrap(); - assert_eq!(set.len(), 5); - assert_eq!(inserted, false); - assert_eq!(i, 0); - assert_eq!(*v, 0); - } - } - - #[test] - fn insert_order() { - 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.insert(elt); - } - - assert_eq!(set.iter().count(), set.len()); - assert_eq!(set.iter().count(), insert.len()); - for (a, b) in insert.iter().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]; - let not_present = [1, 3, 6, 9, 10]; - let mut set = IndexSet::with_capacity(replace.len()); - - for (i, &elt) in replace.iter().enumerate() { - assert_eq!(set.len(), i); - set.replace(elt); - assert_eq!(set.len(), i + 1); - assert_eq!(set.get(&elt), Some(&elt)); - } - println!("{:?}", set); - - for &elt in ¬_present { - assert!(set.get(&elt).is_none()); - } - } - - #[test] - fn replace_full() { - let replace = vec![9, 2, 7, 1, 4, 6, 13]; - let present = vec![1, 6, 2]; - let mut set = IndexSet::with_capacity(replace.len()); - - for (i, &elt) in replace.iter().enumerate() { - assert_eq!(set.len(), i); - let (index, replaced) = set.replace_full(elt); - assert!(replaced.is_none()); - assert_eq!(Some(index), set.get_full(&elt).map(|x| x.0)); - assert_eq!(set.len(), i + 1); - } - - let len = set.len(); - for &elt in &present { - let (index, replaced) = set.replace_full(elt); - assert_eq!(Some(elt), replaced); - assert_eq!(Some(index), set.get_full(&elt).map(|x| x.0)); - assert_eq!(set.len(), len); - } - } - - #[test] - fn replace_2() { - let mut set = IndexSet::with_capacity(16); - - let mut values = vec![]; - values.extend(0..16); - values.extend(if cfg!(miri) { 32..64 } else { 128..267 }); - - for &i in &values { - let old_set = set.clone(); - set.replace(i); - for value in old_set.iter() { - if set.get(value).is_none() { - println!("old_set: {:?}", old_set); - println!("set: {:?}", set); - panic!("did not find {} in set", value); - } - } - } - - for &i in &values { - assert!(set.get(&i).is_some(), "did not find {}", i); - } - } - - #[test] - fn replace_dup() { - let mut elements = vec![0, 2, 4, 6, 8]; - let mut set: IndexSet = elements.drain(..).collect(); - { - let (i, v) = set.get_full(&0).unwrap(); - assert_eq!(set.len(), 5); - assert_eq!(i, 0); - assert_eq!(*v, 0); - } - { - let replaced = set.replace(0); - let (i, v) = set.get_full(&0).unwrap(); - assert_eq!(set.len(), 5); - assert_eq!(replaced, Some(0)); - assert_eq!(i, 0); - assert_eq!(*v, 0); - } - } - - #[test] - fn replace_order() { - let replace = [0, 4, 2, 12, 8, 7, 11, 5, 3, 17, 19, 22, 23]; - let mut set = IndexSet::new(); - - for &elt in &replace { - set.replace(elt); - } - - assert_eq!(set.iter().count(), set.len()); - assert_eq!(set.iter().count(), replace.len()); - for (a, b) in replace.iter().zip(set.iter()) { - assert_eq!(a, b); - } - for (i, v) in (0..replace.len()).zip(set.iter()) { - assert_eq!(set.get_index(i).unwrap(), v); - } - } - - #[test] - fn grow() { - let insert = [0, 4, 2, 12, 8, 7, 11]; - let not_present = [1, 3, 6, 9, 10]; - let mut set = IndexSet::with_capacity(insert.len()); - - for (i, &elt) in insert.iter().enumerate() { - assert_eq!(set.len(), i); - set.insert(elt); - assert_eq!(set.len(), i + 1); - assert_eq!(set.get(&elt), Some(&elt)); - } - - println!("{:?}", set); - for &elt in &insert { - set.insert(elt * 10); - } - for &elt in &insert { - set.insert(elt * 100); - } - for (i, &elt) in insert.iter().cycle().enumerate().take(100) { - set.insert(elt * 100 + i as i32); - } - println!("{:?}", set); - for &elt in ¬_present { - assert!(set.get(&elt).is_none()); - } - } - - #[test] - fn reserve() { - let mut set = IndexSet::::new(); - assert_eq!(set.capacity(), 0); - set.reserve(100); - let capacity = set.capacity(); - assert!(capacity >= 100); - for i in 0..capacity { - assert_eq!(set.len(), i); - set.insert(i); - assert_eq!(set.len(), i + 1); - assert_eq!(set.capacity(), capacity); - assert_eq!(set.get(&i), Some(&i)); - } - set.insert(capacity); - assert_eq!(set.len(), capacity + 1); - assert!(set.capacity() > capacity); - assert_eq!(set.get(&capacity), Some(&capacity)); - } - - #[test] - fn shrink_to_fit() { - let mut set = IndexSet::::new(); - assert_eq!(set.capacity(), 0); - for i in 0..100 { - assert_eq!(set.len(), i); - set.insert(i); - assert_eq!(set.len(), i + 1); - assert!(set.capacity() >= i + 1); - assert_eq!(set.get(&i), Some(&i)); - set.shrink_to_fit(); - assert_eq!(set.len(), i + 1); - assert_eq!(set.capacity(), i + 1); - assert_eq!(set.get(&i), Some(&i)); - } - } - - #[test] - fn remove() { - 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.insert(elt); - } - - assert_eq!(set.iter().count(), set.len()); - assert_eq!(set.iter().count(), insert.len()); - for (a, b) in insert.iter().zip(set.iter()) { - assert_eq!(a, b); - } - - let remove_fail = [99, 77]; - let remove = [4, 12, 8, 7]; - - for &value in &remove_fail { - assert!(set.swap_remove_full(&value).is_none()); - } - println!("{:?}", set); - for &value in &remove { - //println!("{:?}", set); - let index = set.get_full(&value).unwrap().0; - assert_eq!(set.swap_remove_full(&value), Some((index, value))); - } - println!("{:?}", set); - - for value in &insert { - assert_eq!(set.get(value).is_some(), !remove.contains(value)); - } - assert_eq!(set.len(), insert.len() - remove.len()); - assert_eq!(set.iter().count(), insert.len() - remove.len()); - } - - #[test] - fn swap_remove_index() { - 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.insert(elt); - } - - let mut vector = insert.to_vec(); - let remove_sequence = &[3, 3, 10, 4, 5, 4, 3, 0, 1]; - - // check that the same swap remove sequence on vec and set - // have the same result. - for &rm in remove_sequence { - let out_vec = vector.swap_remove(rm); - let out_set = set.swap_remove_index(rm).unwrap(); - assert_eq!(out_vec, out_set); - } - assert_eq!(vector.len(), set.len()); - for (a, b) in vector.iter().zip(set.iter()) { - assert_eq!(a, b); - } - } - - #[test] - fn partial_eq_and_eq() { - let mut set_a = IndexSet::new(); - set_a.insert(1); - set_a.insert(2); - let mut set_b = set_a.clone(); - assert_eq!(set_a, set_b); - set_b.swap_remove(&1); - assert_ne!(set_a, set_b); - - let set_c: IndexSet<_> = set_b.into_iter().collect(); - assert_ne!(set_a, set_c); - assert_ne!(set_c, set_a); - } - - #[test] - fn extend() { - let mut set = IndexSet::new(); - set.extend(vec![&1, &2, &3, &4]); - set.extend(vec![5, 6]); - assert_eq!(set.into_iter().collect::>(), vec![1, 2, 3, 4, 5, 6]); - } - - #[test] - fn comparisons() { - let set_a: IndexSet<_> = (0..3).collect(); - let set_b: IndexSet<_> = (3..6).collect(); - let set_c: IndexSet<_> = (0..6).collect(); - let set_d: IndexSet<_> = (3..9).collect(); - - assert!(!set_a.is_disjoint(&set_a)); - assert!(set_a.is_subset(&set_a)); - assert!(set_a.is_superset(&set_a)); - - assert!(set_a.is_disjoint(&set_b)); - assert!(set_b.is_disjoint(&set_a)); - assert!(!set_a.is_subset(&set_b)); - assert!(!set_b.is_subset(&set_a)); - assert!(!set_a.is_superset(&set_b)); - assert!(!set_b.is_superset(&set_a)); - - assert!(!set_a.is_disjoint(&set_c)); - assert!(!set_c.is_disjoint(&set_a)); - assert!(set_a.is_subset(&set_c)); - assert!(!set_c.is_subset(&set_a)); - assert!(!set_a.is_superset(&set_c)); - assert!(set_c.is_superset(&set_a)); - - assert!(!set_c.is_disjoint(&set_d)); - assert!(!set_d.is_disjoint(&set_c)); - assert!(!set_c.is_subset(&set_d)); - assert!(!set_d.is_subset(&set_c)); - assert!(!set_c.is_superset(&set_d)); - assert!(!set_d.is_superset(&set_c)); - } - - #[test] - fn iter_comparisons() { - use std::iter::empty; - - fn check<'a, I1, I2>(iter1: I1, iter2: I2) - where - I1: Iterator, - I2: Iterator, - { - assert!(iter1.copied().eq(iter2)); - } - - let set_a: IndexSet<_> = (0..3).collect(); - let set_b: IndexSet<_> = (3..6).collect(); - let set_c: IndexSet<_> = (0..6).collect(); - let set_d: IndexSet<_> = (3..9).rev().collect(); - - check(set_a.difference(&set_a), empty()); - check(set_a.symmetric_difference(&set_a), empty()); - check(set_a.intersection(&set_a), 0..3); - check(set_a.union(&set_a), 0..3); - - check(set_a.difference(&set_b), 0..3); - check(set_b.difference(&set_a), 3..6); - check(set_a.symmetric_difference(&set_b), 0..6); - check(set_b.symmetric_difference(&set_a), (3..6).chain(0..3)); - check(set_a.intersection(&set_b), empty()); - check(set_b.intersection(&set_a), empty()); - check(set_a.union(&set_b), 0..6); - check(set_b.union(&set_a), (3..6).chain(0..3)); - - check(set_a.difference(&set_c), empty()); - check(set_c.difference(&set_a), 3..6); - check(set_a.symmetric_difference(&set_c), 3..6); - check(set_c.symmetric_difference(&set_a), 3..6); - check(set_a.intersection(&set_c), 0..3); - check(set_c.intersection(&set_a), 0..3); - check(set_a.union(&set_c), 0..6); - check(set_c.union(&set_a), 0..6); - - check(set_c.difference(&set_d), 0..3); - check(set_d.difference(&set_c), (6..9).rev()); - check( - set_c.symmetric_difference(&set_d), - (0..3).chain((6..9).rev()), - ); - check(set_d.symmetric_difference(&set_c), (6..9).rev().chain(0..3)); - check(set_c.intersection(&set_d), 3..6); - check(set_d.intersection(&set_c), (3..6).rev()); - check(set_c.union(&set_d), (0..6).chain((6..9).rev())); - check(set_d.union(&set_c), (3..9).rev().chain(0..3)); - } - - #[test] - fn ops() { - let empty = IndexSet::::new(); - let set_a: IndexSet<_> = (0..3).collect(); - let set_b: IndexSet<_> = (3..6).collect(); - let set_c: IndexSet<_> = (0..6).collect(); - let set_d: IndexSet<_> = (3..9).rev().collect(); - - #[allow(clippy::eq_op)] - { - assert_eq!(&set_a & &set_a, set_a); - assert_eq!(&set_a | &set_a, set_a); - assert_eq!(&set_a ^ &set_a, empty); - assert_eq!(&set_a - &set_a, empty); - } - - assert_eq!(&set_a & &set_b, empty); - assert_eq!(&set_b & &set_a, empty); - assert_eq!(&set_a | &set_b, set_c); - assert_eq!(&set_b | &set_a, set_c); - assert_eq!(&set_a ^ &set_b, set_c); - assert_eq!(&set_b ^ &set_a, set_c); - assert_eq!(&set_a - &set_b, set_a); - assert_eq!(&set_b - &set_a, set_b); - - assert_eq!(&set_a & &set_c, set_a); - assert_eq!(&set_c & &set_a, set_a); - assert_eq!(&set_a | &set_c, set_c); - assert_eq!(&set_c | &set_a, set_c); - assert_eq!(&set_a ^ &set_c, set_b); - assert_eq!(&set_c ^ &set_a, set_b); - assert_eq!(&set_a - &set_c, empty); - assert_eq!(&set_c - &set_a, set_b); - - assert_eq!(&set_c & &set_d, set_b); - assert_eq!(&set_d & &set_c, set_b); - assert_eq!(&set_c | &set_d, &set_a | &set_d); - assert_eq!(&set_d | &set_c, &set_a | &set_d); - assert_eq!(&set_c ^ &set_d, &set_a | &(&set_d - &set_b)); - assert_eq!(&set_d ^ &set_c, &set_a | &(&set_d - &set_b)); - assert_eq!(&set_c - &set_d, set_a); - assert_eq!(&set_d - &set_c, &set_d - &set_b); - } - - #[test] - #[cfg(feature = "std")] - fn from_array() { - let set1 = IndexSet::from([1, 2, 3, 4]); - let set2: IndexSet<_> = [1, 2, 3, 4].into(); - - assert_eq!(set1, set2); - } -} diff --git a/src/set/tests.rs b/src/set/tests.rs new file mode 100644 index 00000000..0bc800e9 --- /dev/null +++ b/src/set/tests.rs @@ -0,0 +1,523 @@ +use super::*; +use std::string::String; + +#[test] +fn it_works() { + let mut set = IndexSet::new(); + assert_eq!(set.is_empty(), true); + set.insert(1); + set.insert(1); + assert_eq!(set.len(), 1); + assert!(set.get(&1).is_some()); + assert_eq!(set.is_empty(), false); +} + +#[test] +fn new() { + let set = IndexSet::::new(); + println!("{:?}", set); + assert_eq!(set.capacity(), 0); + assert_eq!(set.len(), 0); + assert_eq!(set.is_empty(), true); +} + +#[test] +fn insert() { + let insert = [0, 4, 2, 12, 8, 7, 11, 5]; + let not_present = [1, 3, 6, 9, 10]; + let mut set = IndexSet::with_capacity(insert.len()); + + for (i, &elt) in insert.iter().enumerate() { + assert_eq!(set.len(), i); + set.insert(elt); + assert_eq!(set.len(), i + 1); + assert_eq!(set.get(&elt), Some(&elt)); + } + println!("{:?}", set); + + for &elt in ¬_present { + assert!(set.get(&elt).is_none()); + } +} + +#[test] +fn insert_full() { + let insert = vec![9, 2, 7, 1, 4, 6, 13]; + let present = vec![1, 6, 2]; + let mut set = IndexSet::with_capacity(insert.len()); + + for (i, &elt) in insert.iter().enumerate() { + assert_eq!(set.len(), i); + let (index, success) = set.insert_full(elt); + assert!(success); + assert_eq!(Some(index), set.get_full(&elt).map(|x| x.0)); + assert_eq!(set.len(), i + 1); + } + + let len = set.len(); + for &elt in &present { + let (index, success) = set.insert_full(elt); + assert!(!success); + assert_eq!(Some(index), set.get_full(&elt).map(|x| x.0)); + assert_eq!(set.len(), len); + } +} + +#[test] +fn insert_2() { + let mut set = IndexSet::with_capacity(16); + + let mut values = vec![]; + values.extend(0..16); + values.extend(if cfg!(miri) { 32..64 } else { 128..267 }); + + for &i in &values { + let old_set = set.clone(); + set.insert(i); + for value in old_set.iter() { + if set.get(value).is_none() { + println!("old_set: {:?}", old_set); + println!("set: {:?}", set); + panic!("did not find {} in set", value); + } + } + } + + for &i in &values { + assert!(set.get(&i).is_some(), "did not find {}", i); + } +} + +#[test] +fn insert_dup() { + let mut elements = vec![0, 2, 4, 6, 8]; + let mut set: IndexSet = elements.drain(..).collect(); + { + let (i, v) = set.get_full(&0).unwrap(); + assert_eq!(set.len(), 5); + assert_eq!(i, 0); + assert_eq!(*v, 0); + } + { + let inserted = set.insert(0); + let (i, v) = set.get_full(&0).unwrap(); + assert_eq!(set.len(), 5); + assert_eq!(inserted, false); + assert_eq!(i, 0); + assert_eq!(*v, 0); + } +} + +#[test] +fn insert_order() { + 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.insert(elt); + } + + assert_eq!(set.iter().count(), set.len()); + assert_eq!(set.iter().count(), insert.len()); + for (a, b) in insert.iter().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]; + let not_present = [1, 3, 6, 9, 10]; + let mut set = IndexSet::with_capacity(replace.len()); + + for (i, &elt) in replace.iter().enumerate() { + assert_eq!(set.len(), i); + set.replace(elt); + assert_eq!(set.len(), i + 1); + assert_eq!(set.get(&elt), Some(&elt)); + } + println!("{:?}", set); + + for &elt in ¬_present { + assert!(set.get(&elt).is_none()); + } +} + +#[test] +fn replace_full() { + let replace = vec![9, 2, 7, 1, 4, 6, 13]; + let present = vec![1, 6, 2]; + let mut set = IndexSet::with_capacity(replace.len()); + + for (i, &elt) in replace.iter().enumerate() { + assert_eq!(set.len(), i); + let (index, replaced) = set.replace_full(elt); + assert!(replaced.is_none()); + assert_eq!(Some(index), set.get_full(&elt).map(|x| x.0)); + assert_eq!(set.len(), i + 1); + } + + let len = set.len(); + for &elt in &present { + let (index, replaced) = set.replace_full(elt); + assert_eq!(Some(elt), replaced); + assert_eq!(Some(index), set.get_full(&elt).map(|x| x.0)); + assert_eq!(set.len(), len); + } +} + +#[test] +fn replace_2() { + let mut set = IndexSet::with_capacity(16); + + let mut values = vec![]; + values.extend(0..16); + values.extend(if cfg!(miri) { 32..64 } else { 128..267 }); + + for &i in &values { + let old_set = set.clone(); + set.replace(i); + for value in old_set.iter() { + if set.get(value).is_none() { + println!("old_set: {:?}", old_set); + println!("set: {:?}", set); + panic!("did not find {} in set", value); + } + } + } + + for &i in &values { + assert!(set.get(&i).is_some(), "did not find {}", i); + } +} + +#[test] +fn replace_dup() { + let mut elements = vec![0, 2, 4, 6, 8]; + let mut set: IndexSet = elements.drain(..).collect(); + { + let (i, v) = set.get_full(&0).unwrap(); + assert_eq!(set.len(), 5); + assert_eq!(i, 0); + assert_eq!(*v, 0); + } + { + let replaced = set.replace(0); + let (i, v) = set.get_full(&0).unwrap(); + assert_eq!(set.len(), 5); + assert_eq!(replaced, Some(0)); + assert_eq!(i, 0); + assert_eq!(*v, 0); + } +} + +#[test] +fn replace_order() { + let replace = [0, 4, 2, 12, 8, 7, 11, 5, 3, 17, 19, 22, 23]; + let mut set = IndexSet::new(); + + for &elt in &replace { + set.replace(elt); + } + + assert_eq!(set.iter().count(), set.len()); + assert_eq!(set.iter().count(), replace.len()); + for (a, b) in replace.iter().zip(set.iter()) { + assert_eq!(a, b); + } + for (i, v) in (0..replace.len()).zip(set.iter()) { + assert_eq!(set.get_index(i).unwrap(), v); + } +} + +#[test] +fn grow() { + let insert = [0, 4, 2, 12, 8, 7, 11]; + let not_present = [1, 3, 6, 9, 10]; + let mut set = IndexSet::with_capacity(insert.len()); + + for (i, &elt) in insert.iter().enumerate() { + assert_eq!(set.len(), i); + set.insert(elt); + assert_eq!(set.len(), i + 1); + assert_eq!(set.get(&elt), Some(&elt)); + } + + println!("{:?}", set); + for &elt in &insert { + set.insert(elt * 10); + } + for &elt in &insert { + set.insert(elt * 100); + } + for (i, &elt) in insert.iter().cycle().enumerate().take(100) { + set.insert(elt * 100 + i as i32); + } + println!("{:?}", set); + for &elt in ¬_present { + assert!(set.get(&elt).is_none()); + } +} + +#[test] +fn reserve() { + let mut set = IndexSet::::new(); + assert_eq!(set.capacity(), 0); + set.reserve(100); + let capacity = set.capacity(); + assert!(capacity >= 100); + for i in 0..capacity { + assert_eq!(set.len(), i); + set.insert(i); + assert_eq!(set.len(), i + 1); + assert_eq!(set.capacity(), capacity); + assert_eq!(set.get(&i), Some(&i)); + } + set.insert(capacity); + assert_eq!(set.len(), capacity + 1); + assert!(set.capacity() > capacity); + assert_eq!(set.get(&capacity), Some(&capacity)); +} + +#[test] +fn shrink_to_fit() { + let mut set = IndexSet::::new(); + assert_eq!(set.capacity(), 0); + for i in 0..100 { + assert_eq!(set.len(), i); + set.insert(i); + assert_eq!(set.len(), i + 1); + assert!(set.capacity() >= i + 1); + assert_eq!(set.get(&i), Some(&i)); + set.shrink_to_fit(); + assert_eq!(set.len(), i + 1); + assert_eq!(set.capacity(), i + 1); + assert_eq!(set.get(&i), Some(&i)); + } +} + +#[test] +fn remove() { + 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.insert(elt); + } + + assert_eq!(set.iter().count(), set.len()); + assert_eq!(set.iter().count(), insert.len()); + for (a, b) in insert.iter().zip(set.iter()) { + assert_eq!(a, b); + } + + let remove_fail = [99, 77]; + let remove = [4, 12, 8, 7]; + + for &value in &remove_fail { + assert!(set.swap_remove_full(&value).is_none()); + } + println!("{:?}", set); + for &value in &remove { + //println!("{:?}", set); + let index = set.get_full(&value).unwrap().0; + assert_eq!(set.swap_remove_full(&value), Some((index, value))); + } + println!("{:?}", set); + + for value in &insert { + assert_eq!(set.get(value).is_some(), !remove.contains(value)); + } + assert_eq!(set.len(), insert.len() - remove.len()); + assert_eq!(set.iter().count(), insert.len() - remove.len()); +} + +#[test] +fn swap_remove_index() { + 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.insert(elt); + } + + let mut vector = insert.to_vec(); + let remove_sequence = &[3, 3, 10, 4, 5, 4, 3, 0, 1]; + + // check that the same swap remove sequence on vec and set + // have the same result. + for &rm in remove_sequence { + let out_vec = vector.swap_remove(rm); + let out_set = set.swap_remove_index(rm).unwrap(); + assert_eq!(out_vec, out_set); + } + assert_eq!(vector.len(), set.len()); + for (a, b) in vector.iter().zip(set.iter()) { + assert_eq!(a, b); + } +} + +#[test] +fn partial_eq_and_eq() { + let mut set_a = IndexSet::new(); + set_a.insert(1); + set_a.insert(2); + let mut set_b = set_a.clone(); + assert_eq!(set_a, set_b); + set_b.swap_remove(&1); + assert_ne!(set_a, set_b); + + let set_c: IndexSet<_> = set_b.into_iter().collect(); + assert_ne!(set_a, set_c); + assert_ne!(set_c, set_a); +} + +#[test] +fn extend() { + let mut set = IndexSet::new(); + set.extend(vec![&1, &2, &3, &4]); + set.extend(vec![5, 6]); + assert_eq!(set.into_iter().collect::>(), vec![1, 2, 3, 4, 5, 6]); +} + +#[test] +fn comparisons() { + let set_a: IndexSet<_> = (0..3).collect(); + let set_b: IndexSet<_> = (3..6).collect(); + let set_c: IndexSet<_> = (0..6).collect(); + let set_d: IndexSet<_> = (3..9).collect(); + + assert!(!set_a.is_disjoint(&set_a)); + assert!(set_a.is_subset(&set_a)); + assert!(set_a.is_superset(&set_a)); + + assert!(set_a.is_disjoint(&set_b)); + assert!(set_b.is_disjoint(&set_a)); + assert!(!set_a.is_subset(&set_b)); + assert!(!set_b.is_subset(&set_a)); + assert!(!set_a.is_superset(&set_b)); + assert!(!set_b.is_superset(&set_a)); + + assert!(!set_a.is_disjoint(&set_c)); + assert!(!set_c.is_disjoint(&set_a)); + assert!(set_a.is_subset(&set_c)); + assert!(!set_c.is_subset(&set_a)); + assert!(!set_a.is_superset(&set_c)); + assert!(set_c.is_superset(&set_a)); + + assert!(!set_c.is_disjoint(&set_d)); + assert!(!set_d.is_disjoint(&set_c)); + assert!(!set_c.is_subset(&set_d)); + assert!(!set_d.is_subset(&set_c)); + assert!(!set_c.is_superset(&set_d)); + assert!(!set_d.is_superset(&set_c)); +} + +#[test] +fn iter_comparisons() { + use std::iter::empty; + + fn check<'a, I1, I2>(iter1: I1, iter2: I2) + where + I1: Iterator, + I2: Iterator, + { + assert!(iter1.copied().eq(iter2)); + } + + let set_a: IndexSet<_> = (0..3).collect(); + let set_b: IndexSet<_> = (3..6).collect(); + let set_c: IndexSet<_> = (0..6).collect(); + let set_d: IndexSet<_> = (3..9).rev().collect(); + + check(set_a.difference(&set_a), empty()); + check(set_a.symmetric_difference(&set_a), empty()); + check(set_a.intersection(&set_a), 0..3); + check(set_a.union(&set_a), 0..3); + + check(set_a.difference(&set_b), 0..3); + check(set_b.difference(&set_a), 3..6); + check(set_a.symmetric_difference(&set_b), 0..6); + check(set_b.symmetric_difference(&set_a), (3..6).chain(0..3)); + check(set_a.intersection(&set_b), empty()); + check(set_b.intersection(&set_a), empty()); + check(set_a.union(&set_b), 0..6); + check(set_b.union(&set_a), (3..6).chain(0..3)); + + check(set_a.difference(&set_c), empty()); + check(set_c.difference(&set_a), 3..6); + check(set_a.symmetric_difference(&set_c), 3..6); + check(set_c.symmetric_difference(&set_a), 3..6); + check(set_a.intersection(&set_c), 0..3); + check(set_c.intersection(&set_a), 0..3); + check(set_a.union(&set_c), 0..6); + check(set_c.union(&set_a), 0..6); + + check(set_c.difference(&set_d), 0..3); + check(set_d.difference(&set_c), (6..9).rev()); + check( + set_c.symmetric_difference(&set_d), + (0..3).chain((6..9).rev()), + ); + check(set_d.symmetric_difference(&set_c), (6..9).rev().chain(0..3)); + check(set_c.intersection(&set_d), 3..6); + check(set_d.intersection(&set_c), (3..6).rev()); + check(set_c.union(&set_d), (0..6).chain((6..9).rev())); + check(set_d.union(&set_c), (3..9).rev().chain(0..3)); +} + +#[test] +fn ops() { + let empty = IndexSet::::new(); + let set_a: IndexSet<_> = (0..3).collect(); + let set_b: IndexSet<_> = (3..6).collect(); + let set_c: IndexSet<_> = (0..6).collect(); + let set_d: IndexSet<_> = (3..9).rev().collect(); + + #[allow(clippy::eq_op)] + { + assert_eq!(&set_a & &set_a, set_a); + assert_eq!(&set_a | &set_a, set_a); + assert_eq!(&set_a ^ &set_a, empty); + assert_eq!(&set_a - &set_a, empty); + } + + assert_eq!(&set_a & &set_b, empty); + assert_eq!(&set_b & &set_a, empty); + assert_eq!(&set_a | &set_b, set_c); + assert_eq!(&set_b | &set_a, set_c); + assert_eq!(&set_a ^ &set_b, set_c); + assert_eq!(&set_b ^ &set_a, set_c); + assert_eq!(&set_a - &set_b, set_a); + assert_eq!(&set_b - &set_a, set_b); + + assert_eq!(&set_a & &set_c, set_a); + assert_eq!(&set_c & &set_a, set_a); + assert_eq!(&set_a | &set_c, set_c); + assert_eq!(&set_c | &set_a, set_c); + assert_eq!(&set_a ^ &set_c, set_b); + assert_eq!(&set_c ^ &set_a, set_b); + assert_eq!(&set_a - &set_c, empty); + assert_eq!(&set_c - &set_a, set_b); + + assert_eq!(&set_c & &set_d, set_b); + assert_eq!(&set_d & &set_c, set_b); + assert_eq!(&set_c | &set_d, &set_a | &set_d); + assert_eq!(&set_d | &set_c, &set_a | &set_d); + assert_eq!(&set_c ^ &set_d, &set_a | &(&set_d - &set_b)); + assert_eq!(&set_d ^ &set_c, &set_a | &(&set_d - &set_b)); + assert_eq!(&set_c - &set_d, set_a); + assert_eq!(&set_d - &set_c, &set_d - &set_b); +} + +#[test] +#[cfg(feature = "std")] +fn from_array() { + let set1 = IndexSet::from([1, 2, 3, 4]); + let set2: IndexSet<_> = [1, 2, 3, 4].into(); + + assert_eq!(set1, set2); +} From 0662f01a586c076682974b311fdd265e673a6021 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 12 Jul 2022 14:29:26 -0700 Subject: [PATCH 092/236] Move map iterators to their own file --- src/map.rs | 441 ++------------------------------------------ src/map/iter.rs | 463 +++++++++++++++++++++++++++++++++++++++++++++++ src/map/slice.rs | 32 +--- src/set.rs | 2 +- 4 files changed, 488 insertions(+), 450 deletions(-) create mode 100644 src/map/iter.rs diff --git a/src/map.rs b/src/map.rs index 2bd5b21f..e71c799c 100644 --- a/src/map.rs +++ b/src/map.rs @@ -2,25 +2,28 @@ //! pairs is independent of the hash values of the keys. mod core; +mod iter; mod slice; #[cfg(test)] mod tests; +pub use self::core::{Entry, OccupiedEntry, VacantEntry}; +pub use self::iter::{ + Drain, IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Values, ValuesMut, +}; pub use self::slice::Slice; pub use crate::mutable_keys::MutableKeys; #[cfg(feature = "rayon")] pub use crate::rayon::map as rayon; -use crate::vec::{self, Vec}; use ::core::cmp::Ordering; use ::core::fmt; use ::core::hash::{BuildHasher, Hash, Hasher}; -use ::core::iter::FusedIterator; use ::core::ops::{Index, IndexMut, RangeBounds}; -use ::core::slice::{Iter as SliceIter, IterMut as SliceIterMut}; use alloc::boxed::Box; +use alloc::vec::Vec; #[cfg(feature = "std")] use std::collections::hash_map::RandomState; @@ -30,8 +33,6 @@ use crate::equivalent::Equivalent; use crate::util::{third, try_simplify_range}; use crate::{Bucket, Entries, HashValue}; -pub use self::core::{Entry, OccupiedEntry, VacantEntry}; - /// A hash table where the iteration order of the key-value pairs is independent /// of the hash values of the keys. /// @@ -220,52 +221,38 @@ impl IndexMap { /// Return an iterator over the key-value pairs of the map, in their order pub fn iter(&self) -> Iter<'_, K, V> { - Iter { - iter: self.as_entries().iter(), - } + Iter::new(self.as_entries()) } /// Return an iterator over the key-value pairs of the map, in their order pub fn iter_mut(&mut self) -> IterMut<'_, K, V> { - IterMut { - iter: self.as_entries_mut().iter_mut(), - } + IterMut::new(self.as_entries_mut()) } /// Return an iterator over the keys of the map, in their order pub fn keys(&self) -> Keys<'_, K, V> { - Keys { - iter: self.as_entries().iter(), - } + Keys::new(self.as_entries()) } /// Return an owning iterator over the keys of the map, in their order pub fn into_keys(self) -> IntoKeys { - IntoKeys { - iter: self.into_entries().into_iter(), - } + IntoKeys::new(self.into_entries()) } /// Return an iterator over the values of the map, in their order pub fn values(&self) -> Values<'_, K, V> { - Values { - iter: self.as_entries().iter(), - } + Values::new(self.as_entries()) } /// Return an iterator over mutable references to the values of the map, /// in their order pub fn values_mut(&mut self) -> ValuesMut<'_, K, V> { - ValuesMut { - iter: self.as_entries_mut().iter_mut(), - } + ValuesMut::new(self.as_entries_mut()) } /// Return an owning iterator over the values of the map, in their order pub fn into_values(self) -> IntoValues { - IntoValues { - iter: self.into_entries().into_iter(), - } + IntoValues::new(self.into_entries()) } /// Remove all key-value pairs in the map, while preserving its capacity. @@ -299,9 +286,7 @@ impl IndexMap { where R: RangeBounds, { - Drain { - iter: self.core.drain(range), - } + Drain::new(self.core.drain(range)) } /// Splits the collection into two at the given index. @@ -704,9 +689,7 @@ where { let mut entries = self.into_entries(); entries.sort_by(move |a, b| cmp(&a.key, &a.value, &b.key, &b.value)); - IntoIter { - iter: entries.into_iter(), - } + IntoIter::new(entries) } /// Sort the map's key-value pairs by the default ordering of the keys, but @@ -750,9 +733,7 @@ where { let mut entries = self.into_entries(); entries.sort_unstable_by(move |a, b| cmp(&a.key, &a.value, &b.key, &b.value)); - IntoIter { - iter: entries.into_iter(), - } + IntoIter::new(entries) } /// Reverses the order of the map’s key-value pairs in place. @@ -900,396 +881,6 @@ impl IndexMap { } } -/// An iterator over the keys of a `IndexMap`. -/// -/// This `struct` is created by the [`keys`] method on [`IndexMap`]. See its -/// documentation for more. -/// -/// [`keys`]: struct.IndexMap.html#method.keys -/// [`IndexMap`]: struct.IndexMap.html -pub struct Keys<'a, K, V> { - iter: SliceIter<'a, Bucket>, -} - -impl<'a, K, V> Iterator for Keys<'a, K, V> { - type Item = &'a K; - - iterator_methods!(Bucket::key_ref); -} - -impl DoubleEndedIterator for Keys<'_, K, V> { - double_ended_iterator_methods!(Bucket::key_ref); -} - -impl ExactSizeIterator for Keys<'_, K, V> { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl FusedIterator for Keys<'_, K, V> {} - -// FIXME(#26925) Remove in favor of `#[derive(Clone)]` -impl Clone for Keys<'_, K, V> { - fn clone(&self) -> Self { - Keys { - iter: self.iter.clone(), - } - } -} - -impl fmt::Debug for Keys<'_, K, V> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.clone()).finish() - } -} - -/// An owning iterator over the keys of a `IndexMap`. -/// -/// This `struct` is created by the [`into_keys`] method on [`IndexMap`]. -/// See its documentation for more. -/// -/// [`IndexMap`]: struct.IndexMap.html -/// [`into_keys`]: struct.IndexMap.html#method.into_keys -pub struct IntoKeys { - iter: vec::IntoIter>, -} - -impl Iterator for IntoKeys { - type Item = K; - - iterator_methods!(Bucket::key); -} - -impl DoubleEndedIterator for IntoKeys { - double_ended_iterator_methods!(Bucket::key); -} - -impl ExactSizeIterator for IntoKeys { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl FusedIterator for IntoKeys {} - -impl fmt::Debug for IntoKeys { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let iter = self.iter.as_slice().iter().map(Bucket::key_ref); - f.debug_list().entries(iter).finish() - } -} - -/// An iterator over the values of a `IndexMap`. -/// -/// This `struct` is created by the [`values`] method on [`IndexMap`]. See its -/// documentation for more. -/// -/// [`values`]: struct.IndexMap.html#method.values -/// [`IndexMap`]: struct.IndexMap.html -pub struct Values<'a, K, V> { - iter: SliceIter<'a, Bucket>, -} - -impl<'a, K, V> Iterator for Values<'a, K, V> { - type Item = &'a V; - - iterator_methods!(Bucket::value_ref); -} - -impl DoubleEndedIterator for Values<'_, K, V> { - double_ended_iterator_methods!(Bucket::value_ref); -} - -impl ExactSizeIterator for Values<'_, K, V> { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl FusedIterator for Values<'_, K, V> {} - -// FIXME(#26925) Remove in favor of `#[derive(Clone)]` -impl Clone for Values<'_, K, V> { - fn clone(&self) -> Self { - Values { - iter: self.iter.clone(), - } - } -} - -impl fmt::Debug for Values<'_, K, V> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.clone()).finish() - } -} - -/// A mutable iterator over the values of a `IndexMap`. -/// -/// This `struct` is created by the [`values_mut`] method on [`IndexMap`]. See its -/// documentation for more. -/// -/// [`values_mut`]: struct.IndexMap.html#method.values_mut -/// [`IndexMap`]: struct.IndexMap.html -pub struct ValuesMut<'a, K, V> { - iter: SliceIterMut<'a, Bucket>, -} - -impl<'a, K, V> Iterator for ValuesMut<'a, K, V> { - type Item = &'a mut V; - - iterator_methods!(Bucket::value_mut); -} - -impl DoubleEndedIterator for ValuesMut<'_, K, V> { - double_ended_iterator_methods!(Bucket::value_mut); -} - -impl ExactSizeIterator for ValuesMut<'_, K, V> { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl FusedIterator for ValuesMut<'_, K, V> {} - -impl fmt::Debug for ValuesMut<'_, K, V> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let iter = self.iter.as_slice().iter().map(Bucket::value_ref); - f.debug_list().entries(iter).finish() - } -} - -/// An owning iterator over the values of a `IndexMap`. -/// -/// This `struct` is created by the [`into_values`] method on [`IndexMap`]. -/// See its documentation for more. -/// -/// [`IndexMap`]: struct.IndexMap.html -/// [`into_values`]: struct.IndexMap.html#method.into_values -pub struct IntoValues { - iter: vec::IntoIter>, -} - -impl Iterator for IntoValues { - type Item = V; - - iterator_methods!(Bucket::value); -} - -impl DoubleEndedIterator for IntoValues { - double_ended_iterator_methods!(Bucket::value); -} - -impl ExactSizeIterator for IntoValues { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl FusedIterator for IntoValues {} - -impl fmt::Debug for IntoValues { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let iter = self.iter.as_slice().iter().map(Bucket::value_ref); - f.debug_list().entries(iter).finish() - } -} - -/// An iterator over the entries of a `IndexMap`. -/// -/// This `struct` is created by the [`iter`] method on [`IndexMap`]. See its -/// documentation for more. -/// -/// [`iter`]: struct.IndexMap.html#method.iter -/// [`IndexMap`]: struct.IndexMap.html -pub struct Iter<'a, K, V> { - iter: SliceIter<'a, Bucket>, -} - -impl<'a, K, V> Iter<'a, K, V> { - /// Returns a slice of the remaining entries in the iterator. - pub fn as_slice(&self) -> &'a Slice { - Slice::from_slice(self.iter.as_slice()) - } -} - -impl<'a, K, V> Iterator for Iter<'a, K, V> { - type Item = (&'a K, &'a V); - - iterator_methods!(Bucket::refs); -} - -impl DoubleEndedIterator for Iter<'_, K, V> { - double_ended_iterator_methods!(Bucket::refs); -} - -impl ExactSizeIterator for Iter<'_, K, V> { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl FusedIterator for Iter<'_, K, V> {} - -// FIXME(#26925) Remove in favor of `#[derive(Clone)]` -impl Clone for Iter<'_, K, V> { - fn clone(&self) -> Self { - Iter { - iter: self.iter.clone(), - } - } -} - -impl fmt::Debug for Iter<'_, K, V> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.clone()).finish() - } -} - -/// A mutable iterator over the entries of a `IndexMap`. -/// -/// This `struct` is created by the [`iter_mut`] method on [`IndexMap`]. See its -/// documentation for more. -/// -/// [`iter_mut`]: struct.IndexMap.html#method.iter_mut -/// [`IndexMap`]: struct.IndexMap.html -pub struct IterMut<'a, K, V> { - iter: SliceIterMut<'a, Bucket>, -} - -impl<'a, K, V> IterMut<'a, K, V> { - /// Returns a slice of the remaining entries in the iterator. - /// - /// To avoid creating `&mut` references that alias, this is forced to consume the iterator. - pub fn into_slice(self) -> &'a mut Slice { - Slice::from_mut_slice(self.iter.into_slice()) - } -} - -impl<'a, K, V> Iterator for IterMut<'a, K, V> { - type Item = (&'a K, &'a mut V); - - iterator_methods!(Bucket::ref_mut); -} - -impl DoubleEndedIterator for IterMut<'_, K, V> { - double_ended_iterator_methods!(Bucket::ref_mut); -} - -impl ExactSizeIterator for IterMut<'_, K, V> { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl FusedIterator for IterMut<'_, K, V> {} - -impl fmt::Debug for IterMut<'_, K, V> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let iter = self.iter.as_slice().iter().map(Bucket::refs); - f.debug_list().entries(iter).finish() - } -} - -/// An owning iterator over the entries of a `IndexMap`. -/// -/// This `struct` is created by the [`into_iter`] method on [`IndexMap`] -/// (provided by the `IntoIterator` trait). See its documentation for more. -/// -/// [`into_iter`]: struct.IndexMap.html#method.into_iter -/// [`IndexMap`]: struct.IndexMap.html -pub struct IntoIter { - iter: vec::IntoIter>, -} - -impl Iterator for IntoIter { - type Item = (K, V); - - iterator_methods!(Bucket::key_value); -} - -impl DoubleEndedIterator for IntoIter { - double_ended_iterator_methods!(Bucket::key_value); -} - -impl ExactSizeIterator for IntoIter { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl FusedIterator for IntoIter {} - -impl fmt::Debug for IntoIter { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let iter = self.iter.as_slice().iter().map(Bucket::refs); - f.debug_list().entries(iter).finish() - } -} - -/// A draining iterator over the entries of a `IndexMap`. -/// -/// This `struct` is created by the [`drain`] method on [`IndexMap`]. See its -/// documentation for more. -/// -/// [`drain`]: struct.IndexMap.html#method.drain -/// [`IndexMap`]: struct.IndexMap.html -pub struct Drain<'a, K, V> { - pub(crate) iter: vec::Drain<'a, Bucket>, -} - -impl Iterator for Drain<'_, K, V> { - type Item = (K, V); - - iterator_methods!(Bucket::key_value); -} - -impl DoubleEndedIterator for Drain<'_, K, V> { - double_ended_iterator_methods!(Bucket::key_value); -} - -impl ExactSizeIterator for Drain<'_, K, V> { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl FusedIterator for Drain<'_, K, V> {} - -impl fmt::Debug for Drain<'_, K, V> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let iter = self.iter.as_slice().iter().map(Bucket::refs); - f.debug_list().entries(iter).finish() - } -} - -impl<'a, K, V, S> IntoIterator for &'a IndexMap { - type Item = (&'a K, &'a V); - type IntoIter = Iter<'a, K, V>; - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl<'a, K, V, S> IntoIterator for &'a mut IndexMap { - type Item = (&'a K, &'a mut V); - type IntoIter = IterMut<'a, K, V>; - fn into_iter(self) -> Self::IntoIter { - self.iter_mut() - } -} - -impl IntoIterator for IndexMap { - type Item = (K, V); - type IntoIter = IntoIter; - fn into_iter(self) -> Self::IntoIter { - IntoIter { - iter: self.into_entries().into_iter(), - } - } -} - /// Access `IndexMap` values corresponding to a key. /// /// # Examples diff --git a/src/map/iter.rs b/src/map/iter.rs new file mode 100644 index 00000000..5a439116 --- /dev/null +++ b/src/map/iter.rs @@ -0,0 +1,463 @@ +use super::{Bucket, Entries, IndexMap, Slice}; + +use alloc::vec::{self, Vec}; +use core::fmt; +use core::iter::FusedIterator; +use core::slice; + +impl<'a, K, V, S> IntoIterator for &'a IndexMap { + type Item = (&'a K, &'a V); + type IntoIter = Iter<'a, K, V>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, K, V, S> IntoIterator for &'a mut IndexMap { + type Item = (&'a K, &'a mut V); + type IntoIter = IterMut<'a, K, V>; + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +impl IntoIterator for IndexMap { + type Item = (K, V); + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIter::new(self.into_entries()) + } +} + +/// An iterator over the entries of a `IndexMap`. +/// +/// This `struct` is created by the [`iter`] method on [`IndexMap`]. See its +/// documentation for more. +/// +/// [`iter`]: struct.IndexMap.html#method.iter +/// [`IndexMap`]: struct.IndexMap.html +pub struct Iter<'a, K, V> { + iter: slice::Iter<'a, Bucket>, +} + +impl<'a, K, V> Iter<'a, K, V> { + pub(super) fn new(entries: &'a [Bucket]) -> Self { + Self { + iter: entries.iter(), + } + } + + /// Returns a slice of the remaining entries in the iterator. + pub fn as_slice(&self) -> &'a Slice { + Slice::from_slice(self.iter.as_slice()) + } +} + +impl<'a, K, V> Iterator for Iter<'a, K, V> { + type Item = (&'a K, &'a V); + + iterator_methods!(Bucket::refs); +} + +impl DoubleEndedIterator for Iter<'_, K, V> { + double_ended_iterator_methods!(Bucket::refs); +} + +impl ExactSizeIterator for Iter<'_, K, V> { + fn len(&self) -> usize { + self.iter.len() + } +} + +impl FusedIterator for Iter<'_, K, V> {} + +// FIXME(#26925) Remove in favor of `#[derive(Clone)]` +impl Clone for Iter<'_, K, V> { + fn clone(&self) -> Self { + Iter { + iter: self.iter.clone(), + } + } +} + +impl fmt::Debug for Iter<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.clone()).finish() + } +} + +/// A mutable iterator over the entries of a `IndexMap`. +/// +/// This `struct` is created by the [`iter_mut`] method on [`IndexMap`]. See its +/// documentation for more. +/// +/// [`iter_mut`]: struct.IndexMap.html#method.iter_mut +/// [`IndexMap`]: struct.IndexMap.html +pub struct IterMut<'a, K, V> { + iter: slice::IterMut<'a, Bucket>, +} + +impl<'a, K, V> IterMut<'a, K, V> { + pub(super) fn new(entries: &'a mut [Bucket]) -> Self { + Self { + iter: entries.iter_mut(), + } + } + + /// Returns a slice of the remaining entries in the iterator. + /// + /// To avoid creating `&mut` references that alias, this is forced to consume the iterator. + pub fn into_slice(self) -> &'a mut Slice { + Slice::from_mut_slice(self.iter.into_slice()) + } +} + +impl<'a, K, V> Iterator for IterMut<'a, K, V> { + type Item = (&'a K, &'a mut V); + + iterator_methods!(Bucket::ref_mut); +} + +impl DoubleEndedIterator for IterMut<'_, K, V> { + double_ended_iterator_methods!(Bucket::ref_mut); +} + +impl ExactSizeIterator for IterMut<'_, K, V> { + fn len(&self) -> usize { + self.iter.len() + } +} + +impl FusedIterator for IterMut<'_, K, V> {} + +impl fmt::Debug for IterMut<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let iter = self.iter.as_slice().iter().map(Bucket::refs); + f.debug_list().entries(iter).finish() + } +} + +/// An owning iterator over the entries of a `IndexMap`. +/// +/// This `struct` is created by the [`into_iter`] method on [`IndexMap`] +/// (provided by the `IntoIterator` trait). See its documentation for more. +/// +/// [`into_iter`]: struct.IndexMap.html#method.into_iter +/// [`IndexMap`]: struct.IndexMap.html +pub struct IntoIter { + iter: vec::IntoIter>, +} + +impl IntoIter { + pub(super) fn new(entries: Vec>) -> Self { + Self { + iter: entries.into_iter(), + } + } +} + +impl Iterator for IntoIter { + type Item = (K, V); + + iterator_methods!(Bucket::key_value); +} + +impl DoubleEndedIterator for IntoIter { + double_ended_iterator_methods!(Bucket::key_value); +} + +impl ExactSizeIterator for IntoIter { + fn len(&self) -> usize { + self.iter.len() + } +} + +impl FusedIterator for IntoIter {} + +impl fmt::Debug for IntoIter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let iter = self.iter.as_slice().iter().map(Bucket::refs); + f.debug_list().entries(iter).finish() + } +} + +/// A draining iterator over the entries of a `IndexMap`. +/// +/// This `struct` is created by the [`drain`] method on [`IndexMap`]. See its +/// documentation for more. +/// +/// [`drain`]: struct.IndexMap.html#method.drain +/// [`IndexMap`]: struct.IndexMap.html +pub struct Drain<'a, K, V> { + iter: vec::Drain<'a, Bucket>, +} + +impl<'a, K, V> Drain<'a, K, V> { + pub(super) fn new(iter: vec::Drain<'a, Bucket>) -> Self { + Self { iter } + } +} + +impl Iterator for Drain<'_, K, V> { + type Item = (K, V); + + iterator_methods!(Bucket::key_value); +} + +impl DoubleEndedIterator for Drain<'_, K, V> { + double_ended_iterator_methods!(Bucket::key_value); +} + +impl ExactSizeIterator for Drain<'_, K, V> { + fn len(&self) -> usize { + self.iter.len() + } +} + +impl FusedIterator for Drain<'_, K, V> {} + +impl fmt::Debug for Drain<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let iter = self.iter.as_slice().iter().map(Bucket::refs); + f.debug_list().entries(iter).finish() + } +} + +/// An iterator over the keys of a `IndexMap`. +/// +/// This `struct` is created by the [`keys`] method on [`IndexMap`]. See its +/// documentation for more. +/// +/// [`keys`]: struct.IndexMap.html#method.keys +/// [`IndexMap`]: struct.IndexMap.html +pub struct Keys<'a, K, V> { + iter: slice::Iter<'a, Bucket>, +} + +impl<'a, K, V> Keys<'a, K, V> { + pub(super) fn new(entries: &'a [Bucket]) -> Self { + Self { + iter: entries.iter(), + } + } +} + +impl<'a, K, V> Iterator for Keys<'a, K, V> { + type Item = &'a K; + + iterator_methods!(Bucket::key_ref); +} + +impl DoubleEndedIterator for Keys<'_, K, V> { + double_ended_iterator_methods!(Bucket::key_ref); +} + +impl ExactSizeIterator for Keys<'_, K, V> { + fn len(&self) -> usize { + self.iter.len() + } +} + +impl FusedIterator for Keys<'_, K, V> {} + +// FIXME(#26925) Remove in favor of `#[derive(Clone)]` +impl Clone for Keys<'_, K, V> { + fn clone(&self) -> Self { + Keys { + iter: self.iter.clone(), + } + } +} + +impl fmt::Debug for Keys<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.clone()).finish() + } +} + +/// An owning iterator over the keys of a `IndexMap`. +/// +/// This `struct` is created by the [`into_keys`] method on [`IndexMap`]. +/// See its documentation for more. +/// +/// [`IndexMap`]: struct.IndexMap.html +/// [`into_keys`]: struct.IndexMap.html#method.into_keys +pub struct IntoKeys { + iter: vec::IntoIter>, +} + +impl IntoKeys { + pub(super) fn new(entries: Vec>) -> Self { + Self { + iter: entries.into_iter(), + } + } +} + +impl Iterator for IntoKeys { + type Item = K; + + iterator_methods!(Bucket::key); +} + +impl DoubleEndedIterator for IntoKeys { + double_ended_iterator_methods!(Bucket::key); +} + +impl ExactSizeIterator for IntoKeys { + fn len(&self) -> usize { + self.iter.len() + } +} + +impl FusedIterator for IntoKeys {} + +impl fmt::Debug for IntoKeys { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let iter = self.iter.as_slice().iter().map(Bucket::key_ref); + f.debug_list().entries(iter).finish() + } +} + +/// An iterator over the values of a `IndexMap`. +/// +/// This `struct` is created by the [`values`] method on [`IndexMap`]. See its +/// documentation for more. +/// +/// [`values`]: struct.IndexMap.html#method.values +/// [`IndexMap`]: struct.IndexMap.html +pub struct Values<'a, K, V> { + iter: slice::Iter<'a, Bucket>, +} + +impl<'a, K, V> Values<'a, K, V> { + pub(super) fn new(entries: &'a [Bucket]) -> Self { + Self { + iter: entries.iter(), + } + } +} + +impl<'a, K, V> Iterator for Values<'a, K, V> { + type Item = &'a V; + + iterator_methods!(Bucket::value_ref); +} + +impl DoubleEndedIterator for Values<'_, K, V> { + double_ended_iterator_methods!(Bucket::value_ref); +} + +impl ExactSizeIterator for Values<'_, K, V> { + fn len(&self) -> usize { + self.iter.len() + } +} + +impl FusedIterator for Values<'_, K, V> {} + +// FIXME(#26925) Remove in favor of `#[derive(Clone)]` +impl Clone for Values<'_, K, V> { + fn clone(&self) -> Self { + Values { + iter: self.iter.clone(), + } + } +} + +impl fmt::Debug for Values<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.clone()).finish() + } +} + +/// A mutable iterator over the values of a `IndexMap`. +/// +/// This `struct` is created by the [`values_mut`] method on [`IndexMap`]. See its +/// documentation for more. +/// +/// [`values_mut`]: struct.IndexMap.html#method.values_mut +/// [`IndexMap`]: struct.IndexMap.html +pub struct ValuesMut<'a, K, V> { + iter: slice::IterMut<'a, Bucket>, +} + +impl<'a, K, V> ValuesMut<'a, K, V> { + pub(super) fn new(entries: &'a mut [Bucket]) -> Self { + Self { + iter: entries.iter_mut(), + } + } +} + +impl<'a, K, V> Iterator for ValuesMut<'a, K, V> { + type Item = &'a mut V; + + iterator_methods!(Bucket::value_mut); +} + +impl DoubleEndedIterator for ValuesMut<'_, K, V> { + double_ended_iterator_methods!(Bucket::value_mut); +} + +impl ExactSizeIterator for ValuesMut<'_, K, V> { + fn len(&self) -> usize { + self.iter.len() + } +} + +impl FusedIterator for ValuesMut<'_, K, V> {} + +impl fmt::Debug for ValuesMut<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let iter = self.iter.as_slice().iter().map(Bucket::value_ref); + f.debug_list().entries(iter).finish() + } +} + +/// An owning iterator over the values of a `IndexMap`. +/// +/// This `struct` is created by the [`into_values`] method on [`IndexMap`]. +/// See its documentation for more. +/// +/// [`IndexMap`]: struct.IndexMap.html +/// [`into_values`]: struct.IndexMap.html#method.into_values +pub struct IntoValues { + iter: vec::IntoIter>, +} + +impl IntoValues { + pub(super) fn new(entries: Vec>) -> Self { + Self { + iter: entries.into_iter(), + } + } +} + +impl Iterator for IntoValues { + type Item = V; + + iterator_methods!(Bucket::value); +} + +impl DoubleEndedIterator for IntoValues { + double_ended_iterator_methods!(Bucket::value); +} + +impl ExactSizeIterator for IntoValues { + fn len(&self) -> usize { + self.iter.len() + } +} + +impl FusedIterator for IntoValues {} + +impl fmt::Debug for IntoValues { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let iter = self.iter.as_slice().iter().map(Bucket::value_ref); + f.debug_list().entries(iter).finish() + } +} diff --git a/src/map/slice.rs b/src/map/slice.rs index 4b2029db..9fb876fd 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -169,51 +169,37 @@ impl Slice { /// Return an iterator over the key-value pairs of the map slice. pub fn iter(&self) -> Iter<'_, K, V> { - Iter { - iter: self.entries.iter(), - } + Iter::new(&self.entries) } /// Return an iterator over the key-value pairs of the map slice. pub fn iter_mut(&mut self) -> IterMut<'_, K, V> { - IterMut { - iter: self.entries.iter_mut(), - } + IterMut::new(&mut self.entries) } /// Return an iterator over the keys of the map slice. pub fn keys(&self) -> Keys<'_, K, V> { - Keys { - iter: self.entries.iter(), - } + Keys::new(&self.entries) } /// Return an owning iterator over the keys of the map slice. pub fn into_keys(self: Box) -> IntoKeys { - IntoKeys { - iter: self.into_entries().into_iter(), - } + IntoKeys::new(self.into_entries()) } /// Return an iterator over the values of the map slice. pub fn values(&self) -> Values<'_, K, V> { - Values { - iter: self.entries.iter(), - } + Values::new(&self.entries) } /// Return an iterator over mutable references to the the values of the map slice. pub fn values_mut(&mut self) -> ValuesMut<'_, K, V> { - ValuesMut { - iter: self.entries.iter_mut(), - } + ValuesMut::new(&mut self.entries) } /// Return an owning iterator over the values of the map slice. pub fn into_values(self: Box) -> IntoValues { - IntoValues { - iter: self.into_entries().into_iter(), - } + IntoValues::new(self.into_entries()) } } @@ -240,9 +226,7 @@ impl IntoIterator for Box> { type Item = (K, V); fn into_iter(self) -> Self::IntoIter { - IntoIter { - iter: self.into_entries().into_iter(), - } + IntoIter::new(self.into_entries()) } } diff --git a/src/set.rs b/src/set.rs index 4ef2a9b8..5d66584d 100644 --- a/src/set.rs +++ b/src/set.rs @@ -237,7 +237,7 @@ impl IndexSet { R: RangeBounds, { Drain { - iter: self.map.drain(range).iter, + iter: self.map.core.drain(range), } } From 36c918dd5fcb94a5e93969c5c685c75147c4cb04 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 12 Jul 2022 14:47:54 -0700 Subject: [PATCH 093/236] Move set iterators to their own file --- src/set.rs | 486 +------------------------------------------- src/set/iter.rs | 519 +++++++++++++++++++++++++++++++++++++++++++++++ src/set/slice.rs | 8 +- 3 files changed, 532 insertions(+), 481 deletions(-) create mode 100644 src/set/iter.rs diff --git a/src/set.rs b/src/set.rs index 5d66584d..2f82dd31 100644 --- a/src/set.rs +++ b/src/set.rs @@ -1,10 +1,12 @@ //! A hash set implemented using `IndexMap` +mod iter; mod slice; #[cfg(test)] mod tests; +pub use self::iter::{Difference, Drain, Intersection, IntoIter, Iter, SymmetricDifference, Union}; pub use self::slice::Slice; #[cfg(feature = "rayon")] @@ -14,14 +16,12 @@ pub use crate::rayon::set as rayon; use std::collections::hash_map::RandomState; use crate::util::try_simplify_range; -use crate::vec::{self, Vec}; use alloc::boxed::Box; +use alloc::vec::Vec; use core::cmp::Ordering; use core::fmt; use core::hash::{BuildHasher, Hash}; -use core::iter::{Chain, FusedIterator}; use core::ops::{BitAnd, BitOr, BitXor, Index, RangeBounds, Sub}; -use core::slice::Iter as SliceIter; use super::{Entries, Equivalent, IndexMap}; @@ -200,9 +200,7 @@ impl IndexSet { /// Return an iterator over the values of the set, in their order pub fn iter(&self) -> Iter<'_, T> { - Iter { - iter: self.map.as_entries().iter(), - } + Iter::new(self.as_entries()) } /// Remove all elements in the set, while preserving its capacity. @@ -236,9 +234,7 @@ impl IndexSet { where R: RangeBounds, { - Drain { - iter: self.map.core.drain(range), - } + Drain::new(self.map.core.drain(range)) } /// Splits the collection into two at the given index. @@ -325,10 +321,7 @@ where where S2: BuildHasher, { - Difference { - iter: self.iter(), - other, - } + Difference::new(self, other) } /// Return an iterator over the values that are in `self` or `other`, @@ -343,9 +336,7 @@ where where S2: BuildHasher, { - SymmetricDifference { - iter: self.difference(other).chain(other.difference(self)), - } + SymmetricDifference::new(self, other) } /// Return an iterator over the values that are in both `self` and `other`. @@ -355,10 +346,7 @@ where where S2: BuildHasher, { - Intersection { - iter: self.iter(), - other, - } + Intersection::new(self, other) } /// Return an iterator over all values that are in `self` or `other`. @@ -369,9 +357,7 @@ where where S2: BuildHasher, { - Union { - iter: self.iter().chain(other.difference(self)), - } + Union::new(self, other) } /// Return `true` if an equivalent to `value` exists in the set. @@ -613,9 +599,7 @@ where { let mut entries = self.into_entries(); entries.sort_by(move |a, b| cmp(&a.key, &b.key)); - IntoIter { - iter: entries.into_iter(), - } + IntoIter::new(entries) } /// Sort the set's values by their default ordering. @@ -646,9 +630,7 @@ where { let mut entries = self.into_entries(); entries.sort_unstable_by(move |a, b| cmp(&a.key, &b.key)); - IntoIter { - iter: entries.into_iter(), - } + IntoIter::new(entries) } /// Reverses the order of the set’s values in place. @@ -795,148 +777,6 @@ impl Index for IndexSet { } } -/// An owning iterator over the items of a `IndexSet`. -/// -/// This `struct` is created by the [`into_iter`] method on [`IndexSet`] -/// (provided by the `IntoIterator` trait). See its documentation for more. -/// -/// [`IndexSet`]: struct.IndexSet.html -/// [`into_iter`]: struct.IndexSet.html#method.into_iter -pub struct IntoIter { - iter: vec::IntoIter>, -} - -impl Iterator for IntoIter { - type Item = T; - - iterator_methods!(Bucket::key); -} - -impl DoubleEndedIterator for IntoIter { - double_ended_iterator_methods!(Bucket::key); -} - -impl ExactSizeIterator for IntoIter { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl FusedIterator for IntoIter {} - -impl fmt::Debug for IntoIter { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let iter = self.iter.as_slice().iter().map(Bucket::key_ref); - f.debug_list().entries(iter).finish() - } -} - -/// An iterator over the items of a `IndexSet`. -/// -/// This `struct` is created by the [`iter`] method on [`IndexSet`]. -/// See its documentation for more. -/// -/// [`IndexSet`]: struct.IndexSet.html -/// [`iter`]: struct.IndexSet.html#method.iter -pub struct Iter<'a, T> { - iter: SliceIter<'a, Bucket>, -} - -impl<'a, T> Iter<'a, T> { - /// Returns a slice of the remaining entries in the iterator. - pub fn as_slice(&self) -> &'a Slice { - Slice::from_slice(self.iter.as_slice()) - } -} - -impl<'a, T> Iterator for Iter<'a, T> { - type Item = &'a T; - - iterator_methods!(Bucket::key_ref); -} - -impl DoubleEndedIterator for Iter<'_, T> { - double_ended_iterator_methods!(Bucket::key_ref); -} - -impl ExactSizeIterator for Iter<'_, T> { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl FusedIterator for Iter<'_, T> {} - -impl Clone for Iter<'_, T> { - fn clone(&self) -> Self { - Iter { - iter: self.iter.clone(), - } - } -} - -impl fmt::Debug for Iter<'_, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.clone()).finish() - } -} - -/// A draining iterator over the items of a `IndexSet`. -/// -/// This `struct` is created by the [`drain`] method on [`IndexSet`]. -/// See its documentation for more. -/// -/// [`IndexSet`]: struct.IndexSet.html -/// [`drain`]: struct.IndexSet.html#method.drain -pub struct Drain<'a, T> { - iter: vec::Drain<'a, Bucket>, -} - -impl Iterator for Drain<'_, T> { - type Item = T; - - iterator_methods!(Bucket::key); -} - -impl DoubleEndedIterator for Drain<'_, T> { - double_ended_iterator_methods!(Bucket::key); -} - -impl ExactSizeIterator for Drain<'_, T> { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl FusedIterator for Drain<'_, T> {} - -impl fmt::Debug for Drain<'_, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let iter = self.iter.as_slice().iter().map(Bucket::key_ref); - f.debug_list().entries(iter).finish() - } -} - -impl<'a, T, S> IntoIterator for &'a IndexSet { - type Item = &'a T; - type IntoIter = Iter<'a, T>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl IntoIterator for IndexSet { - type Item = T; - type IntoIter = IntoIter; - - fn into_iter(self) -> Self::IntoIter { - IntoIter { - iter: self.into_entries().into_iter(), - } - } -} - impl FromIterator for IndexSet where T: Hash + Eq, @@ -1055,310 +895,6 @@ where } } -/// A lazy iterator producing elements in the difference of `IndexSet`s. -/// -/// This `struct` is created by the [`difference`] method on [`IndexSet`]. -/// See its documentation for more. -/// -/// [`IndexSet`]: struct.IndexSet.html -/// [`difference`]: struct.IndexSet.html#method.difference -pub struct Difference<'a, T, S> { - iter: Iter<'a, T>, - other: &'a IndexSet, -} - -impl<'a, T, S> Iterator for Difference<'a, T, S> -where - T: Eq + Hash, - S: BuildHasher, -{ - type Item = &'a T; - - fn next(&mut self) -> Option { - while let Some(item) = self.iter.next() { - if !self.other.contains(item) { - return Some(item); - } - } - None - } - - fn size_hint(&self) -> (usize, Option) { - (0, self.iter.size_hint().1) - } -} - -impl DoubleEndedIterator for Difference<'_, T, S> -where - T: Eq + Hash, - S: BuildHasher, -{ - fn next_back(&mut self) -> Option { - while let Some(item) = self.iter.next_back() { - if !self.other.contains(item) { - return Some(item); - } - } - None - } -} - -impl FusedIterator for Difference<'_, T, S> -where - T: Eq + Hash, - S: BuildHasher, -{ -} - -impl Clone for Difference<'_, T, S> { - fn clone(&self) -> Self { - Difference { - iter: self.iter.clone(), - ..*self - } - } -} - -impl fmt::Debug for Difference<'_, T, S> -where - T: fmt::Debug + Eq + Hash, - S: BuildHasher, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.clone()).finish() - } -} - -/// A lazy iterator producing elements in the intersection of `IndexSet`s. -/// -/// This `struct` is created by the [`intersection`] method on [`IndexSet`]. -/// See its documentation for more. -/// -/// [`IndexSet`]: struct.IndexSet.html -/// [`intersection`]: struct.IndexSet.html#method.intersection -pub struct Intersection<'a, T, S> { - iter: Iter<'a, T>, - other: &'a IndexSet, -} - -impl<'a, T, S> Iterator for Intersection<'a, T, S> -where - T: Eq + Hash, - S: BuildHasher, -{ - type Item = &'a T; - - fn next(&mut self) -> Option { - while let Some(item) = self.iter.next() { - if self.other.contains(item) { - return Some(item); - } - } - None - } - - fn size_hint(&self) -> (usize, Option) { - (0, self.iter.size_hint().1) - } -} - -impl DoubleEndedIterator for Intersection<'_, T, S> -where - T: Eq + Hash, - S: BuildHasher, -{ - fn next_back(&mut self) -> Option { - while let Some(item) = self.iter.next_back() { - if self.other.contains(item) { - return Some(item); - } - } - None - } -} - -impl FusedIterator for Intersection<'_, T, S> -where - T: Eq + Hash, - S: BuildHasher, -{ -} - -impl Clone for Intersection<'_, T, S> { - fn clone(&self) -> Self { - Intersection { - iter: self.iter.clone(), - ..*self - } - } -} - -impl fmt::Debug for Intersection<'_, T, S> -where - T: fmt::Debug + Eq + Hash, - S: BuildHasher, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.clone()).finish() - } -} - -/// A lazy iterator producing elements in the symmetric difference of `IndexSet`s. -/// -/// This `struct` is created by the [`symmetric_difference`] method on -/// [`IndexSet`]. See its documentation for more. -/// -/// [`IndexSet`]: struct.IndexSet.html -/// [`symmetric_difference`]: struct.IndexSet.html#method.symmetric_difference -pub struct SymmetricDifference<'a, T, S1, S2> { - iter: Chain, Difference<'a, T, S1>>, -} - -impl<'a, T, S1, S2> Iterator for SymmetricDifference<'a, T, S1, S2> -where - T: Eq + Hash, - S1: BuildHasher, - S2: BuildHasher, -{ - type Item = &'a T; - - fn next(&mut self) -> Option { - self.iter.next() - } - - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - - fn fold(self, init: B, f: F) -> B - where - F: FnMut(B, Self::Item) -> B, - { - self.iter.fold(init, f) - } -} - -impl DoubleEndedIterator for SymmetricDifference<'_, T, S1, S2> -where - T: Eq + Hash, - S1: BuildHasher, - S2: BuildHasher, -{ - fn next_back(&mut self) -> Option { - self.iter.next_back() - } - - fn rfold(self, init: B, f: F) -> B - where - F: FnMut(B, Self::Item) -> B, - { - self.iter.rfold(init, f) - } -} - -impl FusedIterator for SymmetricDifference<'_, T, S1, S2> -where - T: Eq + Hash, - S1: BuildHasher, - S2: BuildHasher, -{ -} - -impl Clone for SymmetricDifference<'_, T, S1, S2> { - fn clone(&self) -> Self { - SymmetricDifference { - iter: self.iter.clone(), - } - } -} - -impl fmt::Debug for SymmetricDifference<'_, T, S1, S2> -where - T: fmt::Debug + Eq + Hash, - S1: BuildHasher, - S2: BuildHasher, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.clone()).finish() - } -} - -/// A lazy iterator producing elements in the union of `IndexSet`s. -/// -/// This `struct` is created by the [`union`] method on [`IndexSet`]. -/// See its documentation for more. -/// -/// [`IndexSet`]: struct.IndexSet.html -/// [`union`]: struct.IndexSet.html#method.union -pub struct Union<'a, T, S> { - iter: Chain, Difference<'a, T, S>>, -} - -impl<'a, T, S> Iterator for Union<'a, T, S> -where - T: Eq + Hash, - S: BuildHasher, -{ - type Item = &'a T; - - fn next(&mut self) -> Option { - self.iter.next() - } - - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - - fn fold(self, init: B, f: F) -> B - where - F: FnMut(B, Self::Item) -> B, - { - self.iter.fold(init, f) - } -} - -impl DoubleEndedIterator for Union<'_, T, S> -where - T: Eq + Hash, - S: BuildHasher, -{ - fn next_back(&mut self) -> Option { - self.iter.next_back() - } - - fn rfold(self, init: B, f: F) -> B - where - F: FnMut(B, Self::Item) -> B, - { - self.iter.rfold(init, f) - } -} - -impl FusedIterator for Union<'_, T, S> -where - T: Eq + Hash, - S: BuildHasher, -{ -} - -impl Clone for Union<'_, T, S> { - fn clone(&self) -> Self { - Union { - iter: self.iter.clone(), - } - } -} - -impl fmt::Debug for Union<'_, T, S> -where - T: fmt::Debug + Eq + Hash, - S: BuildHasher, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.clone()).finish() - } -} - impl BitAnd<&IndexSet> for &IndexSet where T: Eq + Hash + Clone, diff --git a/src/set/iter.rs b/src/set/iter.rs new file mode 100644 index 00000000..de7685bb --- /dev/null +++ b/src/set/iter.rs @@ -0,0 +1,519 @@ +use super::{Bucket, Entries, IndexSet, Slice}; + +use alloc::vec::{self, Vec}; +use core::fmt; +use core::hash::{BuildHasher, Hash}; +use core::iter::{Chain, FusedIterator}; +use core::slice::Iter as SliceIter; + +impl<'a, T, S> IntoIterator for &'a IndexSet { + type Item = &'a T; + type IntoIter = Iter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl IntoIterator for IndexSet { + type Item = T; + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIter::new(self.into_entries()) + } +} + +/// An iterator over the items of a `IndexSet`. +/// +/// This `struct` is created by the [`iter`] method on [`IndexSet`]. +/// See its documentation for more. +/// +/// [`IndexSet`]: struct.IndexSet.html +/// [`iter`]: struct.IndexSet.html#method.iter +pub struct Iter<'a, T> { + iter: SliceIter<'a, Bucket>, +} + +impl<'a, T> Iter<'a, T> { + pub(super) fn new(entries: &'a [Bucket]) -> Self { + Self { + iter: entries.iter(), + } + } + + /// Returns a slice of the remaining entries in the iterator. + pub fn as_slice(&self) -> &'a Slice { + Slice::from_slice(self.iter.as_slice()) + } +} + +impl<'a, T> Iterator for Iter<'a, T> { + type Item = &'a T; + + iterator_methods!(Bucket::key_ref); +} + +impl DoubleEndedIterator for Iter<'_, T> { + double_ended_iterator_methods!(Bucket::key_ref); +} + +impl ExactSizeIterator for Iter<'_, T> { + fn len(&self) -> usize { + self.iter.len() + } +} + +impl FusedIterator for Iter<'_, T> {} + +impl Clone for Iter<'_, T> { + fn clone(&self) -> Self { + Iter { + iter: self.iter.clone(), + } + } +} + +impl fmt::Debug for Iter<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.clone()).finish() + } +} + +/// An owning iterator over the items of a `IndexSet`. +/// +/// This `struct` is created by the [`into_iter`] method on [`IndexSet`] +/// (provided by the `IntoIterator` trait). See its documentation for more. +/// +/// [`IndexSet`]: struct.IndexSet.html +/// [`into_iter`]: struct.IndexSet.html#method.into_iter +pub struct IntoIter { + iter: vec::IntoIter>, +} + +impl IntoIter { + pub(super) fn new(entries: Vec>) -> Self { + Self { + iter: entries.into_iter(), + } + } +} + +impl Iterator for IntoIter { + type Item = T; + + iterator_methods!(Bucket::key); +} + +impl DoubleEndedIterator for IntoIter { + double_ended_iterator_methods!(Bucket::key); +} + +impl ExactSizeIterator for IntoIter { + fn len(&self) -> usize { + self.iter.len() + } +} + +impl FusedIterator for IntoIter {} + +impl fmt::Debug for IntoIter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let iter = self.iter.as_slice().iter().map(Bucket::key_ref); + f.debug_list().entries(iter).finish() + } +} + +/// A draining iterator over the items of a `IndexSet`. +/// +/// This `struct` is created by the [`drain`] method on [`IndexSet`]. +/// See its documentation for more. +/// +/// [`IndexSet`]: struct.IndexSet.html +/// [`drain`]: struct.IndexSet.html#method.drain +pub struct Drain<'a, T> { + iter: vec::Drain<'a, Bucket>, +} + +impl<'a, T> Drain<'a, T> { + pub(super) fn new(iter: vec::Drain<'a, Bucket>) -> Self { + Self { iter } + } +} + +impl Iterator for Drain<'_, T> { + type Item = T; + + iterator_methods!(Bucket::key); +} + +impl DoubleEndedIterator for Drain<'_, T> { + double_ended_iterator_methods!(Bucket::key); +} + +impl ExactSizeIterator for Drain<'_, T> { + fn len(&self) -> usize { + self.iter.len() + } +} + +impl FusedIterator for Drain<'_, T> {} + +impl fmt::Debug for Drain<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let iter = self.iter.as_slice().iter().map(Bucket::key_ref); + f.debug_list().entries(iter).finish() + } +} + +/// A lazy iterator producing elements in the difference of `IndexSet`s. +/// +/// This `struct` is created by the [`difference`] method on [`IndexSet`]. +/// See its documentation for more. +/// +/// [`IndexSet`]: struct.IndexSet.html +/// [`difference`]: struct.IndexSet.html#method.difference +pub struct Difference<'a, T, S> { + iter: Iter<'a, T>, + other: &'a IndexSet, +} + +impl<'a, T, S> Difference<'a, T, S> { + pub(super) fn new(set: &'a IndexSet, other: &'a IndexSet) -> Self { + Self { + iter: set.iter(), + other, + } + } +} + +impl<'a, T, S> Iterator for Difference<'a, T, S> +where + T: Eq + Hash, + S: BuildHasher, +{ + type Item = &'a T; + + fn next(&mut self) -> Option { + while let Some(item) = self.iter.next() { + if !self.other.contains(item) { + return Some(item); + } + } + None + } + + fn size_hint(&self) -> (usize, Option) { + (0, self.iter.size_hint().1) + } +} + +impl DoubleEndedIterator for Difference<'_, T, S> +where + T: Eq + Hash, + S: BuildHasher, +{ + fn next_back(&mut self) -> Option { + while let Some(item) = self.iter.next_back() { + if !self.other.contains(item) { + return Some(item); + } + } + None + } +} + +impl FusedIterator for Difference<'_, T, S> +where + T: Eq + Hash, + S: BuildHasher, +{ +} + +impl Clone for Difference<'_, T, S> { + fn clone(&self) -> Self { + Difference { + iter: self.iter.clone(), + ..*self + } + } +} + +impl fmt::Debug for Difference<'_, T, S> +where + T: fmt::Debug + Eq + Hash, + S: BuildHasher, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.clone()).finish() + } +} + +/// A lazy iterator producing elements in the intersection of `IndexSet`s. +/// +/// This `struct` is created by the [`intersection`] method on [`IndexSet`]. +/// See its documentation for more. +/// +/// [`IndexSet`]: struct.IndexSet.html +/// [`intersection`]: struct.IndexSet.html#method.intersection +pub struct Intersection<'a, T, S> { + iter: Iter<'a, T>, + other: &'a IndexSet, +} + +impl<'a, T, S> Intersection<'a, T, S> { + pub(super) fn new(set: &'a IndexSet, other: &'a IndexSet) -> Self { + Self { + iter: set.iter(), + other, + } + } +} + +impl<'a, T, S> Iterator for Intersection<'a, T, S> +where + T: Eq + Hash, + S: BuildHasher, +{ + type Item = &'a T; + + fn next(&mut self) -> Option { + while let Some(item) = self.iter.next() { + if self.other.contains(item) { + return Some(item); + } + } + None + } + + fn size_hint(&self) -> (usize, Option) { + (0, self.iter.size_hint().1) + } +} + +impl DoubleEndedIterator for Intersection<'_, T, S> +where + T: Eq + Hash, + S: BuildHasher, +{ + fn next_back(&mut self) -> Option { + while let Some(item) = self.iter.next_back() { + if self.other.contains(item) { + return Some(item); + } + } + None + } +} + +impl FusedIterator for Intersection<'_, T, S> +where + T: Eq + Hash, + S: BuildHasher, +{ +} + +impl Clone for Intersection<'_, T, S> { + fn clone(&self) -> Self { + Intersection { + iter: self.iter.clone(), + ..*self + } + } +} + +impl fmt::Debug for Intersection<'_, T, S> +where + T: fmt::Debug + Eq + Hash, + S: BuildHasher, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.clone()).finish() + } +} + +/// A lazy iterator producing elements in the symmetric difference of `IndexSet`s. +/// +/// This `struct` is created by the [`symmetric_difference`] method on +/// [`IndexSet`]. See its documentation for more. +/// +/// [`IndexSet`]: struct.IndexSet.html +/// [`symmetric_difference`]: struct.IndexSet.html#method.symmetric_difference +pub struct SymmetricDifference<'a, T, S1, S2> { + iter: Chain, Difference<'a, T, S1>>, +} + +impl<'a, T, S1, S2> SymmetricDifference<'a, T, S1, S2> +where + T: Eq + Hash, + S1: BuildHasher, + S2: BuildHasher, +{ + pub(super) fn new(set1: &'a IndexSet, set2: &'a IndexSet) -> Self { + let diff1 = set1.difference(set2); + let diff2 = set2.difference(set1); + Self { + iter: diff1.chain(diff2), + } + } +} + +impl<'a, T, S1, S2> Iterator for SymmetricDifference<'a, T, S1, S2> +where + T: Eq + Hash, + S1: BuildHasher, + S2: BuildHasher, +{ + type Item = &'a T; + + fn next(&mut self) -> Option { + self.iter.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } + + fn fold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.iter.fold(init, f) + } +} + +impl DoubleEndedIterator for SymmetricDifference<'_, T, S1, S2> +where + T: Eq + Hash, + S1: BuildHasher, + S2: BuildHasher, +{ + fn next_back(&mut self) -> Option { + self.iter.next_back() + } + + fn rfold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.iter.rfold(init, f) + } +} + +impl FusedIterator for SymmetricDifference<'_, T, S1, S2> +where + T: Eq + Hash, + S1: BuildHasher, + S2: BuildHasher, +{ +} + +impl Clone for SymmetricDifference<'_, T, S1, S2> { + fn clone(&self) -> Self { + SymmetricDifference { + iter: self.iter.clone(), + } + } +} + +impl fmt::Debug for SymmetricDifference<'_, T, S1, S2> +where + T: fmt::Debug + Eq + Hash, + S1: BuildHasher, + S2: BuildHasher, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.clone()).finish() + } +} + +/// A lazy iterator producing elements in the union of `IndexSet`s. +/// +/// This `struct` is created by the [`union`] method on [`IndexSet`]. +/// See its documentation for more. +/// +/// [`IndexSet`]: struct.IndexSet.html +/// [`union`]: struct.IndexSet.html#method.union +pub struct Union<'a, T, S> { + iter: Chain, Difference<'a, T, S>>, +} + +impl<'a, T, S> Union<'a, T, S> +where + T: Eq + Hash, + S: BuildHasher, +{ + pub(super) fn new(set1: &'a IndexSet, set2: &'a IndexSet) -> Self + where + S2: BuildHasher, + { + Self { + iter: set1.iter().chain(set2.difference(set1)), + } + } +} + +impl<'a, T, S> Iterator for Union<'a, T, S> +where + T: Eq + Hash, + S: BuildHasher, +{ + type Item = &'a T; + + fn next(&mut self) -> Option { + self.iter.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } + + fn fold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.iter.fold(init, f) + } +} + +impl DoubleEndedIterator for Union<'_, T, S> +where + T: Eq + Hash, + S: BuildHasher, +{ + fn next_back(&mut self) -> Option { + self.iter.next_back() + } + + fn rfold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.iter.rfold(init, f) + } +} + +impl FusedIterator for Union<'_, T, S> +where + T: Eq + Hash, + S: BuildHasher, +{ +} + +impl Clone for Union<'_, T, S> { + fn clone(&self) -> Self { + Union { + iter: self.iter.clone(), + } + } +} + +impl fmt::Debug for Union<'_, T, S> +where + T: fmt::Debug + Eq + Hash, + S: BuildHasher, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.clone()).finish() + } +} diff --git a/src/set/slice.rs b/src/set/slice.rs index 0924e8b9..608311d2 100644 --- a/src/set/slice.rs +++ b/src/set/slice.rs @@ -107,9 +107,7 @@ impl Slice { /// Return an iterator over the values of the set slice. pub fn iter(&self) -> Iter<'_, T> { - Iter { - iter: self.entries.iter(), - } + Iter::new(&self.entries) } } @@ -127,9 +125,7 @@ impl IntoIterator for Box> { type Item = T; fn into_iter(self) -> Self::IntoIter { - IntoIter { - iter: self.into_entries().into_iter(), - } + IntoIter::new(self.into_entries()) } } From d51af52a6784c6b6628b70a8f235a43bbd994bc7 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 14 Jul 2022 12:27:51 -0700 Subject: [PATCH 094/236] Update to actions/checkout@v3 --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 806da7ec..7b5f310d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: bench: test build benchmarks steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal @@ -63,7 +63,7 @@ jobs: target: thumbv6m-none-eabi steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal @@ -82,7 +82,7 @@ jobs: rust: - beta steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal @@ -98,7 +98,7 @@ jobs: rust: - nightly steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal From 2e3b3c5ff4f28e25e243eec5c65a55b67a57ab50 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 14 Jul 2022 12:31:32 -0700 Subject: [PATCH 095/236] Switch from actions-rs/toolchain to dtolnay/rust-toolchain --- .github/workflows/ci.yml | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b5f310d..959695db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,11 +33,9 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: ${{ matrix.rust }} - override: true - name: Tests run: | cargo build --verbose --features "${{ matrix.features }}" @@ -64,11 +62,9 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: ${{ matrix.rust }} - override: true target: ${{ matrix.target }} - name: Tests run: | @@ -77,32 +73,18 @@ jobs: clippy: runs-on: ubuntu-latest - strategy: - matrix: - rust: - - beta steps: - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@beta with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true components: clippy - run: cargo clippy miri: runs-on: ubuntu-latest - strategy: - matrix: - rust: - - nightly steps: - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@nightly with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true components: miri - run: cargo miri test From aa4ee74bec3f64f452aa107fecd2c1bd04abe66b Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 15 Jul 2022 10:02:16 -0700 Subject: [PATCH 096/236] Revert "Run CI on 1.56.1 until next hashbrown release" This reverts commit b6aa495bede3ae3ed3e1303d35646e9ab719378c. CI should be find on 1.56.0 as of hashbrown 0.12.2. --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 959695db..b20a3e35 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: include: - - rust: 1.56.1 # MSRV + - rust: 1.56.0 # MSRV features: - rust: stable features: serde @@ -55,7 +55,7 @@ jobs: strategy: matrix: include: - - rust: 1.56.1 + - rust: 1.56.0 target: thumbv6m-none-eabi - rust: stable target: thumbv6m-none-eabi From 0589eb8fa5030a0137ea1372fd4465dd09dee518 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 11 Nov 2022 13:28:04 -0800 Subject: [PATCH 097/236] Upgrade to hashbrown 0.13 (MSRV 1.61) --- .github/workflows/ci.yml | 4 ++-- Cargo.toml | 4 ++-- README.md | 2 +- RELEASES.md | 4 ++++ src/lib.rs | 2 +- src/map/core/raw.rs | 16 ++++++++-------- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b20a3e35..959f115e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: include: - - rust: 1.56.0 # MSRV + - rust: 1.61.0 # MSRV features: - rust: stable features: serde @@ -55,7 +55,7 @@ jobs: strategy: matrix: include: - - rust: 1.56.0 + - rust: 1.61.0 target: thumbv6m-none-eabi - rust: stable target: thumbv6m-none-eabi diff --git a/Cargo.toml b/Cargo.toml index 2037f0b2..bed342ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ license = "Apache-2.0 OR MIT" description = "A hash table with consistent order and fast iteration." keywords = ["hashmap", "no_std"] categories = ["data-structures", "no-std"] -rust-version = "1.56" +rust-version = "1.61" [lib] bench = false @@ -23,7 +23,7 @@ rayon = { version = "1.4.1", optional = true } rustc-rayon = { version = "0.4", optional = true } [dependencies.hashbrown] -version = "0.12" +version = "0.13" default-features = false features = ["raw"] diff --git a/README.md b/README.md index d80b7099..95ceef5a 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![build status](https://github.com/bluss/indexmap/workflows/Continuous%20integration/badge.svg?branch=master)](https://github.com/bluss/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.56%2B-orange.svg)](https://img.shields.io/badge/rust-1.56%2B-orange.svg) +[![rustc](https://img.shields.io/badge/rust-1.61%2B-orange.svg)](https://img.shields.io/badge/rust-1.61%2B-orange.svg) A pure-Rust hash table which preserves (in a limited sense) insertion order. diff --git a/RELEASES.md b/RELEASES.md index 651f8b4e..a3497679 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,7 @@ - 2.0.0 (pending) + - **MSRV**: Rust 1.61.0 or later is now required. + - The `"std"` feature is no longer auto-detected. It is included in the default feature set, or else can be enabled like any other Cargo feature. @@ -15,6 +17,8 @@ comparison traits like `Eq` only consider items in order, rather than hash lookups, and slices even implement `Hash`. + - The `hashbrown` dependency has been updated to version 0.13. + - 1.9.1 - The MSRV now allows Rust 1.56.0 as well. However, currently `hashbrown` diff --git a/src/lib.rs b/src/lib.rs index 3d796ea7..597d4e85 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,7 +53,7 @@ //! //! ### Rust Version //! -//! This version of indexmap requires Rust 1.56 or later. +//! This version of indexmap requires Rust 1.61 or later. //! //! The indexmap 2.x release series will use a carefully considered version //! upgrade policy, where in a later 2.x version, we will raise the minimum diff --git a/src/map/core/raw.rs b/src/map/core/raw.rs index bf1672d5..c716fd9b 100644 --- a/src/map/core/raw.rs +++ b/src/map/core/raw.rs @@ -26,7 +26,7 @@ pub(super) struct DebugIndices<'a>(pub &'a RawTable); impl fmt::Debug for DebugIndices<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // SAFETY: we're not letting any of the buckets escape this function - let indices = unsafe { self.0.iter().map(|raw_bucket| raw_bucket.read()) }; + let indices = unsafe { self.0.iter().map(|raw_bucket| *raw_bucket.as_ref()) }; f.debug_list().entries(indices).finish() } } @@ -38,10 +38,10 @@ impl IndexMapCore { unsafe { let offset = end - start; for bucket in self.indices.iter() { - let i = bucket.read(); - if i >= end { - bucket.write(i - offset); - } else if i >= start { + let i = bucket.as_mut(); + if *i >= end { + *i -= offset; + } else if *i >= start { self.indices.erase(bucket); } } @@ -92,8 +92,8 @@ impl IndexMapCore { unsafe { let raw_bucket_a = self.find_index(a); let raw_bucket_b = self.find_index(b); - raw_bucket_a.write(b); - raw_bucket_b.write(a); + *raw_bucket_a.as_mut() = b; + *raw_bucket_b.as_mut() = a; } self.entries.swap(a, b); } @@ -151,7 +151,7 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { #[inline] pub fn index(&self) -> usize { // SAFETY: we have &mut map keep keeping the bucket stable - unsafe { self.raw_bucket.read() } + unsafe { *self.raw_bucket.as_ref() } } /// Converts into a mutable reference to the entry's value in the map, From 3e78c62b964b0cb1e19506156511df7384b708f1 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 16 Nov 2022 18:22:31 -0800 Subject: [PATCH 098/236] impl Arbitrary for IndexMap and IndexSet This implements both `arbitrary::Arbitrary` and `quickcheck::Arbitrary` behind optional dependencies on their respective crates. --- .github/workflows/ci.yml | 6 +++- Cargo.toml | 4 ++- src/arbitrary.rs | 75 ++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 4 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 src/arbitrary.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 959f115e..46086a70 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,11 +19,15 @@ jobs: - rust: 1.61.0 # MSRV features: - rust: stable - features: serde + features: arbitrary + - rust: stable + features: quickcheck - rust: stable features: rayon - rust: stable features: rustc-rayon + - rust: stable + features: serde - rust: stable features: std - rust: beta diff --git a/Cargo.toml b/Cargo.toml index bed342ec..01bac980 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,8 @@ rust-version = "1.61" bench = false [dependencies] +arbitrary = { version = "1.0", optional = true, default-features = false } +quickcheck = { version = "1.0", optional = true, default-features = false } serde = { version = "1.0", optional = true, default-features = false } rayon = { version = "1.4.1", optional = true } @@ -51,7 +53,7 @@ no-dev-version = true tag-name = "{{version}}" [package.metadata.docs.rs] -features = ["serde", "rayon"] +features = ["arbitrary", "quickcheck", "serde", "rayon"] [workspace] members = ["test-nostd", "test-serde"] diff --git a/src/arbitrary.rs b/src/arbitrary.rs new file mode 100644 index 00000000..1347c8b5 --- /dev/null +++ b/src/arbitrary.rs @@ -0,0 +1,75 @@ +#[cfg(feature = "arbitrary")] +mod impl_arbitrary { + use crate::{IndexMap, IndexSet}; + use arbitrary::{Arbitrary, Result, Unstructured}; + use core::hash::{BuildHasher, Hash}; + + impl<'a, K, V, S> Arbitrary<'a> for IndexMap + where + K: Arbitrary<'a> + Hash + Eq, + V: Arbitrary<'a>, + S: BuildHasher + Default, + { + fn arbitrary(u: &mut Unstructured<'a>) -> Result { + u.arbitrary_iter()?.collect() + } + + fn arbitrary_take_rest(u: Unstructured<'a>) -> Result { + u.arbitrary_take_rest_iter()?.collect() + } + } + + impl<'a, T, S> Arbitrary<'a> for IndexSet + where + T: Arbitrary<'a> + Hash + Eq, + S: BuildHasher + Default, + { + fn arbitrary(u: &mut Unstructured<'a>) -> Result { + u.arbitrary_iter()?.collect() + } + + fn arbitrary_take_rest(u: Unstructured<'a>) -> Result { + u.arbitrary_take_rest_iter()?.collect() + } + } +} + +#[cfg(feature = "quickcheck")] +mod impl_quickcheck { + use crate::{IndexMap, IndexSet}; + use alloc::boxed::Box; + use alloc::vec::Vec; + use core::hash::{BuildHasher, Hash}; + use quickcheck::{Arbitrary, Gen}; + + impl Arbitrary for IndexMap + where + K: Arbitrary + Hash + Eq, + V: Arbitrary, + S: BuildHasher + Default + Clone + 'static, + { + fn arbitrary(g: &mut Gen) -> Self { + Self::from_iter(Vec::arbitrary(g)) + } + + fn shrink(&self) -> Box> { + let vec = Vec::from_iter(self.clone()); + Box::new(vec.shrink().map(Self::from_iter)) + } + } + + impl Arbitrary for IndexSet + where + T: Arbitrary + Hash + Eq, + S: BuildHasher + Default + Clone + 'static, + { + fn arbitrary(g: &mut Gen) -> Self { + Self::from_iter(Vec::arbitrary(g)) + } + + fn shrink(&self) -> Box> { + let vec = Vec::from_iter(self.clone()); + Box::new(vec.shrink().map(Self::from_iter)) + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 597d4e85..f9d3c83a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,6 +84,7 @@ extern crate std; use alloc::vec::{self, Vec}; +mod arbitrary; #[macro_use] mod macros; mod equivalent; From cc4c47d22fdc06b16a370d035a6c3354504b483f Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 17 Nov 2022 10:36:10 -0800 Subject: [PATCH 099/236] Add an Arbitrary release note --- RELEASES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index a3497679..cc54a9b4 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -17,6 +17,9 @@ comparison traits like `Eq` only consider items in order, rather than hash lookups, and slices even implement `Hash`. + - `IndexMap` and `IndexSet` both implement `arbitrary::Arbitrary<'_>` and + `quickcheck::Arbitrary` if those optional dependency features are enabled. + - The `hashbrown` dependency has been updated to version 0.13. - 1.9.1 From 8b7f398b2218c6da431d4cecf69ffe91ff066deb Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 16 Nov 2022 10:32:37 -0800 Subject: [PATCH 100/236] Add `sort_by_cached_key` and `par_sort_by_cached_key` --- Cargo.toml | 2 +- RELEASES.md | 4 ++++ src/map.rs | 18 ++++++++++++++++++ src/rayon/map.rs | 12 ++++++++++++ src/rayon/set.rs | 11 +++++++++++ src/set.rs | 18 ++++++++++++++++++ tests/quick.rs | 6 ++++++ 7 files changed, 70 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 01bac980..5b9cba6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ bench = false arbitrary = { version = "1.0", optional = true, default-features = false } quickcheck = { version = "1.0", optional = true, default-features = false } serde = { version = "1.0", optional = true, default-features = false } -rayon = { version = "1.4.1", optional = true } +rayon = { version = "1.5.3", optional = true } # Internal feature, only used when building as part of rustc, # not part of the stable interface of this crate. diff --git a/RELEASES.md b/RELEASES.md index cc54a9b4..21ae5a41 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -20,6 +20,10 @@ - `IndexMap` and `IndexSet` both implement `arbitrary::Arbitrary<'_>` and `quickcheck::Arbitrary` if those optional dependency features are enabled. + - `IndexMap` and `IndexSet` now have `sort_by_cached_key` and + `par_sort_by_cached_key` methods which perform stable sorts in place + using a key extraction function. + - The `hashbrown` dependency has been updated to version 0.13. - 1.9.1 diff --git a/src/map.rs b/src/map.rs index e71c799c..0d45331e 100644 --- a/src/map.rs +++ b/src/map.rs @@ -736,6 +736,24 @@ where IntoIter::new(entries) } + /// Sort the map’s key-value pairs in place using a sort-key extraction function. + /// + /// During sorting, the function is called at most once per entry, by using temporary storage + /// to remember the results of its evaluation. The order of calls to the function is + /// unspecified and may change between versions of `indexmap` or the standard library. + /// + /// Computes in **O(m n + n log n + c)** time () and **O(n)** space, where the function is + /// **O(m)**, *n* is the length of the map, and *c* the capacity. The sort is stable. + pub fn sort_by_cached_key(&mut self, mut sort_key: F) + where + T: Ord, + F: FnMut(&K, &V) -> T, + { + self.with_entries(move |entries| { + entries.sort_by_cached_key(move |a| sort_key(&a.key, &a.value)); + }); + } + /// Reverses the order of the map’s key-value pairs in place. /// /// Computes in **O(n)** time and **O(1)** space. diff --git a/src/rayon/map.rs b/src/rayon/map.rs index 7559d549..23a318c7 100644 --- a/src/rayon/map.rs +++ b/src/rayon/map.rs @@ -504,6 +504,18 @@ where entries.par_sort_unstable_by(move |a, b| cmp(&a.key, &a.value, &b.key, &b.value)); IntoParIter { entries } } + + /// Sort the map’s key-value pairs in place and in parallel, using a sort-key extraction + /// function. + pub fn par_sort_by_cached_key(&mut self, sort_key: F) + where + T: Ord + Send, + F: Fn(&K, &V) -> T + Sync, + { + self.with_entries(move |entries| { + entries.par_sort_by_cached_key(move |a| sort_key(&a.key, &a.value)); + }); + } } /// A parallel mutable iterator over the values of a `IndexMap`. diff --git a/src/rayon/set.rs b/src/rayon/set.rs index 0dc553fc..9ecce331 100644 --- a/src/rayon/set.rs +++ b/src/rayon/set.rs @@ -572,6 +572,17 @@ where entries.par_sort_unstable_by(move |a, b| cmp(&a.key, &b.key)); IntoParIter { entries } } + + /// Sort the set’s values in place and in parallel, using a key extraction function. + pub fn par_sort_by_cached_key(&mut self, sort_key: F) + where + K: Ord + Send, + F: Fn(&T) -> K + Sync, + { + self.with_entries(move |entries| { + entries.par_sort_by_cached_key(move |a| sort_key(&a.key)); + }); + } } /// Requires crate feature `"rayon"`. diff --git a/src/set.rs b/src/set.rs index 2f82dd31..a760c51d 100644 --- a/src/set.rs +++ b/src/set.rs @@ -633,6 +633,24 @@ where IntoIter::new(entries) } + /// Sort the set’s values in place using a key extraction function. + /// + /// During sorting, the function is called at most once per entry, by using temporary storage + /// to remember the results of its evaluation. The order of calls to the function is + /// unspecified and may change between versions of `indexmap` or the standard library. + /// + /// Computes in **O(m n + n log n + c)** time () and **O(n)** space, where the function is + /// **O(m)**, *n* is the length of the map, and *c* the capacity. The sort is stable. + pub fn sort_by_cached_key(&mut self, mut sort_key: F) + where + K: Ord, + F: FnMut(&T) -> K, + { + self.with_entries(move |entries| { + entries.sort_by_cached_key(move |a| sort_key(&a.key)); + }); + } + /// Reverses the order of the set’s values in place. /// /// Computes in **O(n)** time and **O(1)** space. diff --git a/tests/quick.rs b/tests/quick.rs index e9d96acc..4142e2d6 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -452,6 +452,12 @@ quickcheck_limit! { assert_sorted_by_key(map, |t| t.1); } + fn sort_3(keyvals: Large>) -> () { + let mut map: IndexMap<_, _> = IndexMap::from_iter(keyvals.to_vec()); + map.sort_by_cached_key(|&k, _| std::cmp::Reverse(k)); + assert_sorted_by_key(map, |t| std::cmp::Reverse(t.0)); + } + fn reverse(keyvals: Large>) -> () { let mut map: IndexMap<_, _> = IndexMap::from_iter(keyvals.to_vec()); From 0d40bae470ff7435cd6ff8ce2feb41742e80ab69 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 16 Nov 2022 12:30:47 -0800 Subject: [PATCH 101/236] Add `reserve_exact`, `try_reserve`, `try_reserve_exact` --- src/lib.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++++++ src/map.rs | 33 ++++++++++++++++++++++++++++- src/map/core.rs | 33 +++++++++++++++++++++++++++++ src/map/tests.rs | 9 ++++++++ src/set.rs | 32 ++++++++++++++++++++++++++++ src/set/tests.rs | 9 ++++++++ 6 files changed, 170 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index f9d3c83a..f295c7c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -191,3 +191,58 @@ trait Entries { where F: FnOnce(&mut [Self::Entry]); } + +/// The error type for `try_reserve` methods. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct TryReserveError { + kind: TryReserveErrorKind, +} + +#[derive(Clone, PartialEq, Eq, Debug)] +enum TryReserveErrorKind { + // The standard library's kind is currently opaque to us, otherwise we could unify this. + Std(alloc::collections::TryReserveError), + CapacityOverflow, + AllocError { layout: alloc::alloc::Layout }, +} + +// These are not `From` so we don't expose them in our public API. +impl TryReserveError { + fn from_alloc(error: alloc::collections::TryReserveError) -> Self { + Self { + kind: TryReserveErrorKind::Std(error), + } + } + + fn from_hashbrown(error: hashbrown::TryReserveError) -> Self { + Self { + kind: match error { + hashbrown::TryReserveError::CapacityOverflow => { + TryReserveErrorKind::CapacityOverflow + } + hashbrown::TryReserveError::AllocError { layout } => { + TryReserveErrorKind::AllocError { layout } + } + }, + } + } +} + +impl core::fmt::Display for TryReserveError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let reason = match &self.kind { + TryReserveErrorKind::Std(e) => return core::fmt::Display::fmt(e, f), + TryReserveErrorKind::CapacityOverflow => { + " because the computed capacity exceeded the collection's maximum" + } + TryReserveErrorKind::AllocError { .. } => { + " because the memory allocator returned an error" + } + }; + f.write_str("memory allocation failed")?; + f.write_str(reason) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TryReserveError {} diff --git a/src/map.rs b/src/map.rs index 0d45331e..f65cb581 100644 --- a/src/map.rs +++ b/src/map.rs @@ -31,7 +31,7 @@ use std::collections::hash_map::RandomState; use self::core::IndexMapCore; use crate::equivalent::Equivalent; use crate::util::{third, try_simplify_range}; -use crate::{Bucket, Entries, HashValue}; +use crate::{Bucket, Entries, HashValue, TryReserveError}; /// A hash table where the iteration order of the key-value pairs is independent /// of the hash values of the keys. @@ -319,6 +319,37 @@ where self.core.reserve(additional); } + /// Reserve capacity for `additional` more key-value pairs, without over-allocating. + /// + /// Unlike `reserve`, this does not deliberately over-allocate the entry capacity to avoid + /// frequent re-allocations. However, the underlying data structures may still have internal + /// capacity requirements, and the allocator itself may give more space than requested, so this + /// cannot be relied upon to be precisely minimal. + /// + /// Computes in **O(n)** time. + pub fn reserve_exact(&mut self, additional: usize) { + self.core.reserve_exact(additional); + } + + /// Try to reserve capacity for `additional` more key-value pairs. + /// + /// Computes in **O(n)** time. + pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> { + self.core.try_reserve(additional) + } + + /// Try to reserve capacity for `additional` more key-value pairs, without over-allocating. + /// + /// Unlike `try_reserve`, this does not deliberately over-allocate the entry capacity to avoid + /// frequent re-allocations. However, the underlying data structures may still have internal + /// capacity requirements, and the allocator itself may give more space than requested, so this + /// cannot be relied upon to be precisely minimal. + /// + /// Computes in **O(n)** time. + pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), TryReserveError> { + self.core.try_reserve_exact(additional) + } + /// Shrink the capacity of the map as much as possible. /// /// Computes in **O(n)** time. diff --git a/src/map/core.rs b/src/map/core.rs index ea7aaae6..0af92171 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -12,6 +12,7 @@ mod raw; use hashbrown::raw::RawTable; use crate::vec::{Drain, Vec}; +use crate::TryReserveError; use core::cmp; use core::fmt; use core::mem::replace; @@ -202,6 +203,38 @@ impl IndexMapCore { self.entries.reserve_exact(additional); } + /// Reserve capacity for `additional` more key-value pairs, without over-allocating. + pub(crate) fn reserve_exact(&mut self, additional: usize) { + self.indices.reserve(additional, get_hash(&self.entries)); + self.entries.reserve_exact(additional); + } + + /// Try to reserve capacity for `additional` more key-value pairs. + pub(crate) fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> { + self.indices + .try_reserve(additional, get_hash(&self.entries)) + .map_err(TryReserveError::from_hashbrown)?; + self.try_reserve_entries() + } + + /// Try to reserve entries capacity to match the indices + fn try_reserve_entries(&mut self) -> Result<(), TryReserveError> { + let additional = self.indices.capacity() - self.entries.len(); + self.entries + .try_reserve_exact(additional) + .map_err(TryReserveError::from_alloc) + } + + /// Try to reserve capacity for `additional` more key-value pairs, without over-allocating. + pub(crate) fn try_reserve_exact(&mut self, additional: usize) -> Result<(), TryReserveError> { + self.indices + .try_reserve(additional, get_hash(&self.entries)) + .map_err(TryReserveError::from_hashbrown)?; + self.entries + .try_reserve_exact(additional) + .map_err(TryReserveError::from_alloc) + } + /// Shrink the capacity of the map with a lower bound pub(crate) fn shrink_to(&mut self, min_capacity: usize) { self.indices diff --git a/src/map/tests.rs b/src/map/tests.rs index b6c6a42d..423e3f3d 100644 --- a/src/map/tests.rs +++ b/src/map/tests.rs @@ -158,6 +158,15 @@ fn reserve() { assert_eq!(map.get(&capacity), Some(&std::usize::MAX)); } +#[test] +fn try_reserve() { + let mut map = IndexMap::::new(); + assert_eq!(map.capacity(), 0); + assert_eq!(map.try_reserve(100), Ok(())); + assert!(map.capacity() >= 100); + assert!(map.try_reserve(usize::MAX).is_err()); +} + #[test] fn shrink_to_fit() { let mut map = IndexMap::::new(); diff --git a/src/set.rs b/src/set.rs index a760c51d..3e6e153b 100644 --- a/src/set.rs +++ b/src/set.rs @@ -11,6 +11,7 @@ pub use self::slice::Slice; #[cfg(feature = "rayon")] pub use crate::rayon::set as rayon; +use crate::TryReserveError; #[cfg(feature = "std")] use std::collections::hash_map::RandomState; @@ -266,6 +267,37 @@ where self.map.reserve(additional); } + /// Reserve capacity for `additional` more values, without over-allocating. + /// + /// Unlike `reserve`, this does not deliberately over-allocate the entry capacity to avoid + /// frequent re-allocations. However, the underlying data structures may still have internal + /// capacity requirements, and the allocator itself may give more space than requested, so this + /// cannot be relied upon to be precisely minimal. + /// + /// Computes in **O(n)** time. + pub fn reserve_exact(&mut self, additional: usize) { + self.map.reserve_exact(additional); + } + + /// Try to reserve capacity for `additional` more values. + /// + /// Computes in **O(n)** time. + pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> { + self.map.try_reserve(additional) + } + + /// Try to reserve capacity for `additional` more values, without over-allocating. + /// + /// Unlike `try_reserve`, this does not deliberately over-allocate the entry capacity to avoid + /// frequent re-allocations. However, the underlying data structures may still have internal + /// capacity requirements, and the allocator itself may give more space than requested, so this + /// cannot be relied upon to be precisely minimal. + /// + /// Computes in **O(n)** time. + pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), TryReserveError> { + self.map.try_reserve_exact(additional) + } + /// Shrink the capacity of the set as much as possible. /// /// Computes in **O(n)** time. diff --git a/src/set/tests.rs b/src/set/tests.rs index 0bc800e9..39e110f4 100644 --- a/src/set/tests.rs +++ b/src/set/tests.rs @@ -282,6 +282,15 @@ fn reserve() { assert_eq!(set.get(&capacity), Some(&capacity)); } +#[test] +fn try_reserve() { + let mut set = IndexSet::::new(); + assert_eq!(set.capacity(), 0); + assert_eq!(set.try_reserve(100), Ok(())); + assert!(set.capacity() >= 100); + assert!(set.try_reserve(usize::MAX).is_err()); +} + #[test] fn shrink_to_fit() { let mut set = IndexSet::::new(); From dbf20da82109584acb33be9cce1d2dd68ea4cabb Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 16 Nov 2022 16:14:41 -0800 Subject: [PATCH 102/236] Be more careful about reserving entries capacity Blindly following the indices capacity could make the entries exceed the maximum layout size, `isize::MAX`, so now we use a limit against that. Furthermore, even within that limit we can *try* the reservation first, and fall back to the minimum requested growth if that fails. This shouldn't matter in practice for 64-bit targets, but smaller targets could potentially run close to the max allocation size. --- src/map/core.rs | 54 +++++++++++++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/map/core.rs b/src/map/core.rs index 0af92171..fa26c28d 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -13,9 +13,8 @@ use hashbrown::raw::RawTable; use crate::vec::{Drain, Vec}; use crate::TryReserveError; -use core::cmp; use core::fmt; -use core::mem::replace; +use core::mem; use core::ops::RangeBounds; use crate::equivalent::Equivalent; @@ -63,18 +62,18 @@ where V: Clone, { fn clone(&self) -> Self { - let indices = self.indices.clone(); - let mut entries = Vec::with_capacity(indices.capacity()); - entries.clone_from(&self.entries); - IndexMapCore { indices, entries } + let mut new = Self::new(); + new.clone_from(self); + new } fn clone_from(&mut self, other: &Self) { let hasher = get_hash(&other.entries); self.indices.clone_from_with_hasher(&other.indices, hasher); if self.entries.capacity() < other.entries.len() { - // If we must resize, match the indices capacity - self.reserve_entries(); + // If we must resize, match the indices capacity. + let additional = other.entries.len() - self.entries.len(); + self.reserve_entries(additional); } self.entries.clone_from(&other.entries); } @@ -121,6 +120,9 @@ impl Entries for IndexMapCore { } impl IndexMapCore { + /// The maximum capacity before the `entries` allocation would exceed `isize::MAX`. + const MAX_ENTRIES_CAPACITY: usize = (isize::MAX as usize) / mem::size_of::>(); + #[inline] pub(crate) const fn new() -> Self { IndexMapCore { @@ -144,7 +146,7 @@ impl IndexMapCore { #[inline] pub(crate) fn capacity(&self) -> usize { - cmp::min(self.indices.capacity(), self.entries.capacity()) + Ord::min(self.indices.capacity(), self.entries.capacity()) } pub(crate) fn clear(&mut self) { @@ -194,12 +196,18 @@ impl IndexMapCore { /// Reserve capacity for `additional` more key-value pairs. pub(crate) fn reserve(&mut self, additional: usize) { self.indices.reserve(additional, get_hash(&self.entries)); - self.reserve_entries(); + self.reserve_entries(additional); } - /// Reserve entries capacity to match the indices - fn reserve_entries(&mut self) { - let additional = self.indices.capacity() - self.entries.len(); + /// Reserve entries capacity, rounded up to match the indices + fn reserve_entries(&mut self, additional: usize) { + // Use a soft-limit on the maximum capacity, but if the caller explicitly + // requested more, do it and let them have the resulting panic. + let new_capacity = Ord::min(self.indices.capacity(), Self::MAX_ENTRIES_CAPACITY); + let try_add = new_capacity - self.entries.len(); + if try_add > additional && self.entries.try_reserve_exact(try_add).is_ok() { + return; + } self.entries.reserve_exact(additional); } @@ -214,12 +222,18 @@ impl IndexMapCore { self.indices .try_reserve(additional, get_hash(&self.entries)) .map_err(TryReserveError::from_hashbrown)?; - self.try_reserve_entries() + self.try_reserve_entries(additional) } - /// Try to reserve entries capacity to match the indices - fn try_reserve_entries(&mut self) -> Result<(), TryReserveError> { - let additional = self.indices.capacity() - self.entries.len(); + /// Try to reserve entries capacity, rounded up to match the indices + fn try_reserve_entries(&mut self, additional: usize) -> Result<(), TryReserveError> { + // Use a soft-limit on the maximum capacity, but if the caller explicitly + // requested more, do it and let them have the resulting error. + let new_capacity = Ord::min(self.indices.capacity(), Self::MAX_ENTRIES_CAPACITY); + let try_add = new_capacity - self.entries.len(); + if try_add > additional && self.entries.try_reserve_exact(try_add).is_ok() { + return Ok(()); + } self.entries .try_reserve_exact(additional) .map_err(TryReserveError::from_alloc) @@ -261,7 +275,7 @@ impl IndexMapCore { if i == self.entries.capacity() { // Reserve our own capacity synced to the indices, // rather than letting `Vec::push` just double it. - self.reserve_entries(); + self.reserve_entries(1); } self.entries.push(Bucket { hash, key, value }); i @@ -281,7 +295,7 @@ impl IndexMapCore { K: Eq, { match self.get_index_of(hash, &key) { - Some(i) => (i, Some(replace(&mut self.entries[i].value, value))), + Some(i) => (i, Some(mem::replace(&mut self.entries[i].value, value))), None => (self.push(hash, key, value), None), } } @@ -634,7 +648,7 @@ pub use self::raw::OccupiedEntry; impl OccupiedEntry<'_, K, V> { /// Sets the value of the entry to `value`, and returns the entry's old value. pub fn insert(&mut self, value: V) -> V { - replace(self.get_mut(), value) + mem::replace(self.get_mut(), value) } /// Remove the key, value pair stored in the map for this entry, and return the value. From 570e1b2809b1712f96ead3f12505e17d97d53a23 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 17 Nov 2022 12:14:59 -0800 Subject: [PATCH 103/236] Add a release note for new reserve methods --- RELEASES.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index 21ae5a41..a71a6b68 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -24,6 +24,11 @@ `par_sort_by_cached_key` methods which perform stable sorts in place using a key extraction function. + - `IndexMap` and `IndexSet` now have `reserve_exact`, `try_reserve`, and + `try_reserve_exact` methods that correspond to the same methods on `Vec`. + However, exactness only applies to the direct capacity for items, while the + raw hash table still follows its own rules for capacity and load factor. + - The `hashbrown` dependency has been updated to version 0.13. - 1.9.1 From 882c8f9ed7ad6450a5efedcbd6c18466922779d4 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 17 Nov 2022 12:36:00 -0800 Subject: [PATCH 104/236] Only grow entries if necessary, since we also round up capacity --- src/map/core.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/map/core.rs b/src/map/core.rs index fa26c28d..4c879c1d 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -196,7 +196,10 @@ impl IndexMapCore { /// Reserve capacity for `additional` more key-value pairs. pub(crate) fn reserve(&mut self, additional: usize) { self.indices.reserve(additional, get_hash(&self.entries)); - self.reserve_entries(additional); + // Only grow entries if necessary, since we also round up capacity. + if additional > self.entries.capacity() - self.entries.len() { + self.reserve_entries(additional); + } } /// Reserve entries capacity, rounded up to match the indices @@ -222,7 +225,12 @@ impl IndexMapCore { self.indices .try_reserve(additional, get_hash(&self.entries)) .map_err(TryReserveError::from_hashbrown)?; - self.try_reserve_entries(additional) + // Only grow entries if necessary, since we also round up capacity. + if additional > self.entries.capacity() - self.entries.len() { + self.try_reserve_entries(additional) + } else { + Ok(()) + } } /// Try to reserve entries capacity, rounded up to match the indices From ca5f848e10c31e80aeaad0720d14aa2f6dd6cfb1 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 17 Nov 2022 13:26:07 -0800 Subject: [PATCH 105/236] Release 1.9.2 --- RELEASES.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index a71a6b68..99a527a2 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -17,9 +17,6 @@ comparison traits like `Eq` only consider items in order, rather than hash lookups, and slices even implement `Hash`. - - `IndexMap` and `IndexSet` both implement `arbitrary::Arbitrary<'_>` and - `quickcheck::Arbitrary` if those optional dependency features are enabled. - - `IndexMap` and `IndexSet` now have `sort_by_cached_key` and `par_sort_by_cached_key` methods which perform stable sorts in place using a key extraction function. @@ -31,6 +28,11 @@ - The `hashbrown` dependency has been updated to version 0.13. +- 1.9.2 + + - `IndexMap` and `IndexSet` both implement `arbitrary::Arbitrary<'_>` and + `quickcheck::Arbitrary` if those optional dependency features are enabled. + - 1.9.1 - The MSRV now allows Rust 1.56.0 as well. However, currently `hashbrown` From d0243c34abc8a0af88432a3a2601331e5d645756 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 18 Nov 2022 08:59:47 -0800 Subject: [PATCH 106/236] Use Vec::retain_mut --- src/map/core.rs | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/src/map/core.rs b/src/map/core.rs index 4c879c1d..12ff8de8 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -502,25 +502,9 @@ impl IndexMapCore { where F: FnMut(&mut K, &mut V) -> bool, { - // FIXME: This could use Vec::retain_mut with MSRV 1.61. - // Like Vec::retain in self.entries, but with mutable K and V. - // We swap-shift all the items we want to keep, truncate the rest, - // then rebuild the raw hash table with the new indexes. - let len = self.entries.len(); - let mut n_deleted = 0; - for i in 0..len { - let will_keep = { - let entry = &mut self.entries[i]; - keep(&mut entry.key, &mut entry.value) - }; - if !will_keep { - n_deleted += 1; - } else if n_deleted > 0 { - self.entries.swap(i - n_deleted, i); - } - } - if n_deleted > 0 { - self.entries.truncate(len - n_deleted); + self.entries + .retain_mut(|entry| keep(&mut entry.key, &mut entry.value)); + if self.entries.len() < self.indices.len() { self.rebuild_hash_table(); } } From a45f31e33823172d5a4d9c4a66bfabfcaa8f11ca Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 18 Nov 2022 09:58:11 -0800 Subject: [PATCH 107/236] Add more slicing methods to iterators, like std In fact, these are directly relying on the same methods from `std` iterators, since we build our iterators as simple wrappers on those. --- src/map/iter.rs | 20 ++++++++++++++++++++ src/set/iter.rs | 10 ++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/map/iter.rs b/src/map/iter.rs index 5a439116..6cd933e9 100644 --- a/src/map/iter.rs +++ b/src/map/iter.rs @@ -108,6 +108,11 @@ impl<'a, K, V> IterMut<'a, K, V> { } /// Returns a slice of the remaining entries in the iterator. + pub fn as_slice(&self) -> &Slice { + Slice::from_slice(self.iter.as_slice()) + } + + /// Returns a mutable slice of the remaining entries in the iterator. /// /// To avoid creating `&mut` references that alias, this is forced to consume the iterator. pub fn into_slice(self) -> &'a mut Slice { @@ -157,6 +162,16 @@ impl IntoIter { iter: entries.into_iter(), } } + + /// Returns a slice of the remaining entries in the iterator. + pub fn as_slice(&self) -> &Slice { + Slice::from_slice(self.iter.as_slice()) + } + + /// Returns a mutable slice of the remaining entries in the iterator. + pub fn as_mut_slice(&mut self) -> &mut Slice { + Slice::from_mut_slice(self.iter.as_mut_slice()) + } } impl Iterator for IntoIter { @@ -199,6 +214,11 @@ impl<'a, K, V> Drain<'a, K, V> { pub(super) fn new(iter: vec::Drain<'a, Bucket>) -> Self { Self { iter } } + + /// Returns a slice of the remaining entries in the iterator. + pub fn as_slice(&self) -> &Slice { + Slice::from_slice(self.iter.as_slice()) + } } impl Iterator for Drain<'_, K, V> { diff --git a/src/set/iter.rs b/src/set/iter.rs index de7685bb..24b3e86c 100644 --- a/src/set/iter.rs +++ b/src/set/iter.rs @@ -97,6 +97,11 @@ impl IntoIter { iter: entries.into_iter(), } } + + /// Returns a slice of the remaining entries in the iterator. + pub fn as_slice(&self) -> &Slice { + Slice::from_slice(self.iter.as_slice()) + } } impl Iterator for IntoIter { @@ -139,6 +144,11 @@ impl<'a, T> Drain<'a, T> { pub(super) fn new(iter: vec::Drain<'a, Bucket>) -> Self { Self { iter } } + + /// Returns a slice of the remaining entries in the iterator. + pub fn as_slice(&self) -> &Slice { + Slice::from_slice(self.iter.as_slice()) + } } impl Iterator for Drain<'_, T> { From 9753b892ac2bb33e72b2f5e2994482dd17d067d0 Mon Sep 17 00:00:00 2001 From: Dirk Stolle Date: Fri, 27 Jan 2023 00:10:45 +0100 Subject: [PATCH 108/236] Fix some typos --- src/map/core.rs | 4 ++-- src/set.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/map/core.rs b/src/map/core.rs index 12ff8de8..908d1e50 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -394,7 +394,7 @@ impl IndexMapCore { 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. + // Use a sentinel 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. @@ -406,7 +406,7 @@ impl IndexMapCore { self.entries[to..=from].rotate_right(1); } - // Change the sentinal index to its final position. + // Change the sentinel index to its final position. update_index(&mut self.indices, from_hash, usize::MAX, to); } } diff --git a/src/set.rs b/src/set.rs index 3e6e153b..260598a8 100644 --- a/src/set.rs +++ b/src/set.rs @@ -644,7 +644,7 @@ where self.map.sort_unstable_keys() } - /// Sort the set's values in place using the comparison funtion `cmp`. + /// Sort the set's values in place using the comparison function `cmp`. /// /// Computes in **O(n log n)** time. The sort is unstable. pub fn sort_unstable_by(&mut self, mut cmp: F) From e66f3b2570fe08cd1feed841aaf56c088c2cc73c Mon Sep 17 00:00:00 2001 From: Richard Berry Date: Mon, 30 Jan 2023 20:50:40 +0000 Subject: [PATCH 109/236] Highlight feature-gated items in documentation --- Cargo.toml | 3 ++- src/lib.rs | 4 ++++ src/macros.rs | 2 ++ src/map.rs | 3 +++ src/rayon/map.rs | 16 ++-------------- src/rayon/set.rs | 20 ++++++++++---------- src/serde.rs | 6 ++---- src/serde_seq.rs | 12 ++---------- src/set.rs | 2 ++ 9 files changed, 29 insertions(+), 39 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5b9cba6e..5bd6fae8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,8 @@ no-dev-version = true tag-name = "{{version}}" [package.metadata.docs.rs] -features = ["arbitrary", "quickcheck", "serde", "rayon"] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] [workspace] members = ["test-nostd", "test-serde"] diff --git a/src/lib.rs b/src/lib.rs index f295c7c4..e7c67931 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,6 +76,8 @@ //! //! [def]: map/struct.IndexMap.html#impl-Default +#![cfg_attr(docsrs, feature(doc_cfg))] + extern crate alloc; #[cfg(feature = "std")] @@ -92,6 +94,7 @@ mod mutable_keys; #[cfg(feature = "serde")] mod serde; #[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] pub mod serde_seq; mod util; @@ -245,4 +248,5 @@ impl core::fmt::Display for TryReserveError { } #[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl std::error::Error for TryReserveError {} diff --git a/src/macros.rs b/src/macros.rs index 889dbe7e..5317f1c9 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,4 +1,5 @@ #[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] #[macro_export] /// Create an `IndexMap` from a list of key-value pairs /// @@ -35,6 +36,7 @@ macro_rules! indexmap { } #[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] #[macro_export] /// Create an `IndexSet` from a list of values /// diff --git a/src/map.rs b/src/map.rs index f65cb581..3c86d6c8 100644 --- a/src/map.rs +++ b/src/map.rs @@ -148,6 +148,7 @@ where } #[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl IndexMap { /// Create a new map. (Does not allocate.) #[inline] @@ -1112,6 +1113,8 @@ where } #[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] + impl From<[(K, V); N]> for IndexMap where K: Hash + Eq, diff --git a/src/rayon/map.rs b/src/rayon/map.rs index 23a318c7..a1f3588b 100644 --- a/src/rayon/map.rs +++ b/src/rayon/map.rs @@ -2,8 +2,8 @@ //! //! You will rarely need to interact with this module directly unless you need to name one of the //! iterator types. -//! -//! Requires crate feature `"rayon"` + +#![cfg_attr(docsrs, doc(cfg(feature = "rayon")))] use super::collect; use rayon::iter::plumbing::{Consumer, ProducerCallback, UnindexedConsumer}; @@ -21,7 +21,6 @@ use crate::Bucket; use crate::Entries; use crate::IndexMap; -/// Requires crate feature `"rayon"`. impl IntoParallelIterator for IndexMap where K: Send, @@ -37,7 +36,6 @@ where } } -/// Requires crate feature `"rayon"`. impl IntoParallelIterator for Box> where K: Send, @@ -81,7 +79,6 @@ impl IndexedParallelIterator for IntoParIter { indexed_parallel_iterator_methods!(Bucket::key_value); } -/// Requires crate feature `"rayon"`. impl<'a, K, V, S> IntoParallelIterator for &'a IndexMap where K: Sync, @@ -97,7 +94,6 @@ where } } -/// Requires crate feature `"rayon"`. impl<'a, K, V> IntoParallelIterator for &'a Slice where K: Sync, @@ -147,7 +143,6 @@ impl IndexedParallelIterator for ParIter<'_, K, V> { indexed_parallel_iterator_methods!(Bucket::refs); } -/// Requires crate feature `"rayon"`. impl<'a, K, V, S> IntoParallelIterator for &'a mut IndexMap where K: Sync + Send, @@ -163,7 +158,6 @@ where } } -/// Requires crate feature `"rayon"`. impl<'a, K, V> IntoParallelIterator for &'a mut Slice where K: Sync + Send, @@ -207,7 +201,6 @@ impl IndexedParallelIterator for ParIterMut<'_, K, V> { indexed_parallel_iterator_methods!(Bucket::ref_mut); } -/// Requires crate feature `"rayon"`. impl<'a, K, V, S> ParallelDrainRange for &'a mut IndexMap where K: Send, @@ -395,7 +388,6 @@ impl IndexedParallelIterator for ParValues<'_, K, V> { indexed_parallel_iterator_methods!(Bucket::value_ref); } -/// Requires crate feature `"rayon"`. impl IndexMap where K: Send, @@ -412,7 +404,6 @@ where } } -/// Requires crate feature `"rayon"`. impl Slice where K: Send, @@ -546,7 +537,6 @@ impl IndexedParallelIterator for ParValuesMut<'_, K, V> { indexed_parallel_iterator_methods!(Bucket::value_mut); } -/// Requires crate feature `"rayon"`. impl FromParallelIterator<(K, V)> for IndexMap where K: Eq + Hash + Send, @@ -567,7 +557,6 @@ where } } -/// Requires crate feature `"rayon"`. impl ParallelExtend<(K, V)> for IndexMap where K: Eq + Hash + Send, @@ -584,7 +573,6 @@ where } } -/// Requires crate feature `"rayon"`. impl<'a, K: 'a, V: 'a, S> ParallelExtend<(&'a K, &'a V)> for IndexMap where K: Copy + Eq + Hash + Send + Sync, diff --git a/src/rayon/set.rs b/src/rayon/set.rs index 9ecce331..9b8abadf 100644 --- a/src/rayon/set.rs +++ b/src/rayon/set.rs @@ -2,8 +2,8 @@ //! //! You will rarely need to interact with this module directly unless you need to name one of the //! iterator types. -//! -//! Requires crate feature `"rayon"`. + +#![cfg_attr(docsrs, doc(cfg(feature = "rayon")))] use super::collect; use rayon::iter::plumbing::{Consumer, ProducerCallback, UnindexedConsumer}; @@ -22,7 +22,7 @@ use crate::IndexSet; type Bucket = crate::Bucket; -/// Requires crate feature `"rayon"`. +#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))] impl IntoParallelIterator for IndexSet where T: Send, @@ -37,7 +37,7 @@ where } } -/// Requires crate feature `"rayon"`. +#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))] impl IntoParallelIterator for Box> where T: Send, @@ -80,7 +80,7 @@ impl IndexedParallelIterator for IntoParIter { indexed_parallel_iterator_methods!(Bucket::key); } -/// Requires crate feature `"rayon"`. +#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))] impl<'a, T, S> IntoParallelIterator for &'a IndexSet where T: Sync, @@ -95,7 +95,7 @@ where } } -/// Requires crate feature `"rayon"`. +#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))] impl<'a, T> IntoParallelIterator for &'a Slice where T: Sync, @@ -144,7 +144,7 @@ impl IndexedParallelIterator for ParIter<'_, T> { indexed_parallel_iterator_methods!(Bucket::key_ref); } -/// Requires crate feature `"rayon"`. +#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))] impl<'a, T, S> ParallelDrainRange for &'a mut IndexSet where T: Send, @@ -585,7 +585,7 @@ where } } -/// Requires crate feature `"rayon"`. +#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))] impl FromParallelIterator for IndexSet where T: Eq + Hash + Send, @@ -605,7 +605,7 @@ where } } -/// Requires crate feature `"rayon"`. +#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))] impl ParallelExtend for IndexSet where T: Eq + Hash + Send, @@ -621,7 +621,7 @@ where } } -/// Requires crate feature `"rayon"`. +#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))] impl<'a, T: 'a, S> ParallelExtend<&'a T> for IndexSet where T: Copy + Eq + Hash + Send + Sync, diff --git a/src/serde.rs b/src/serde.rs index d7473d39..6e55d206 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -1,3 +1,5 @@ +#![cfg_attr(docsrs, doc(cfg(feature = "serde")))] + use serde::de::value::{MapDeserializer, SeqDeserializer}; use serde::de::{ Deserialize, Deserializer, Error, IntoDeserializer, MapAccess, SeqAccess, Visitor, @@ -10,7 +12,6 @@ use core::marker::PhantomData; use crate::IndexMap; -/// Requires crate feature `"serde"` impl Serialize for IndexMap where K: Serialize + Hash + Eq, @@ -54,7 +55,6 @@ where } } -/// Requires crate feature `"serde"` impl<'de, K, V, S> Deserialize<'de> for IndexMap where K: Deserialize<'de> + Eq + Hash, @@ -85,7 +85,6 @@ where use crate::IndexSet; -/// Requires crate feature `"serde"` impl Serialize for IndexSet where T: Serialize + Hash + Eq, @@ -127,7 +126,6 @@ where } } -/// Requires crate feature `"serde"` impl<'de, T, S> Deserialize<'de> for IndexSet where T: Deserialize<'de> + Eq + Hash, diff --git a/src/serde_seq.rs b/src/serde_seq.rs index 618e871f..30eab86c 100644 --- a/src/serde_seq.rs +++ b/src/serde_seq.rs @@ -17,8 +17,8 @@ //! // ... //! } //! ``` -//! -//! Requires crate feature `"serde"` + +#![cfg_attr(docsrs, doc(cfg(feature = "serde")))] use serde::de::{Deserialize, Deserializer, SeqAccess, Visitor}; use serde::ser::{Serialize, Serializer}; @@ -35,8 +35,6 @@ use crate::IndexMap; /// /// This behaves like [`crate::serde_seq`] for `IndexMap`, serializing a sequence /// of `(key, value)` pairs, rather than as a map that might not preserve order. -/// -/// Requires crate feature `"serde"` impl Serialize for MapSlice where K: Serialize, @@ -51,8 +49,6 @@ where } /// Serializes a `set::Slice` as an ordered sequence. -/// -/// Requires crate feature `"serde"` impl Serialize for SetSlice where T: Serialize, @@ -79,8 +75,6 @@ where /// // ... /// } /// ``` -/// -/// Requires crate feature `"serde"` pub fn serialize(map: &IndexMap, serializer: T) -> Result where K: Serialize + Hash + Eq, @@ -135,8 +129,6 @@ where /// // ... /// } /// ``` -/// -/// Requires crate feature `"serde"` pub fn deserialize<'de, D, K, V, S>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, diff --git a/src/set.rs b/src/set.rs index 260598a8..035d5d73 100644 --- a/src/set.rs +++ b/src/set.rs @@ -135,6 +135,7 @@ where } #[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl IndexSet { /// Create a new set. (Does not allocate.) pub fn new() -> Self { @@ -841,6 +842,7 @@ where } #[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl From<[T; N]> for IndexSet where T: Eq + Hash, From 193157ceb9951ee8298af8ca19c931a8eb72260c Mon Sep 17 00:00:00 2001 From: Richard Berry Date: Mon, 30 Jan 2023 21:13:00 +0000 Subject: [PATCH 110/236] Improve feature flag documentation --- src/lib.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index e7c67931..c2015781 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ //! [`IndexSet`]: set/struct.IndexSet.html //! //! -//! ### Feature Highlights +//! ### Highlights //! //! [`IndexMap`] and [`IndexSet`] are drop-in compatible with the std `HashMap` //! and `HashSet`, but they also have some features of note: @@ -26,6 +26,28 @@ //! - The [`MutableKeys`][map::MutableKeys] trait, which gives opt-in mutable //! access to hash map keys. //! +//! ### Feature Flags +//! +//! To reduce the amount of compiled code in the crate by default, certain +//! features are gated behind [feature flags]. These allow you to opt in to (or +//! out of) functionality. Below is a list of the features available in this +//! crate. +//! +//! * `std`: Enables features which require the Rust standard library. For more +//! information see the section on [`no_std`]. +//! * `rayon`: Enables parallel iteration and other parallel methods. +//! * `serde`: Adds implementations for [`Serialize`] and [`Deserialize`] +//! to [`IndexMap`] and [`IndexSet`]. Alternative implementations for +//! (de)serializing [`IndexMap`] as an ordered sequence are available in the +//! [`serde_seq`] module. +//! +//! _Note: only the `std` feature is enabled by default._ +//! +//! [feature flags]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section +//! [`no_std`]: #no-standard-library-targets +//! [`Serialize`]: `::serde::Serialize` +//! [`Deserialize`]: `::serde::Deserialize` +//! //! ### Alternate Hashers //! //! [`IndexMap`] and [`IndexSet`] have a default hasher type `S = RandomState`, From 370413180d6e8b9771cb6e1d939fc132d6081fca Mon Sep 17 00:00:00 2001 From: Richard Berry Date: Mon, 27 Feb 2023 14:18:18 +0000 Subject: [PATCH 111/236] Tidy up feature flag documentation --- Cargo.toml | 2 +- src/lib.rs | 2 ++ src/rayon/map.rs | 2 -- src/rayon/set.rs | 10 ---------- src/serde.rs | 2 -- src/serde_seq.rs | 2 -- 6 files changed, 3 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5bd6fae8..a3d73c01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ no-dev-version = true tag-name = "{{version}}" [package.metadata.docs.rs] -all-features = true +all-features = ["arbitrary", "quickcheck", "serde", "rayon"] rustdoc-args = ["--cfg", "docsrs"] [workspace] diff --git a/src/lib.rs b/src/lib.rs index c2015781..74ac7ea7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -114,6 +114,7 @@ mod macros; mod equivalent; mod mutable_keys; #[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] mod serde; #[cfg(feature = "serde")] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] @@ -126,6 +127,7 @@ pub mod set; // Placed after `map` and `set` so new `rayon` methods on the types // are documented after the "normal" methods. #[cfg(feature = "rayon")] +#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))] mod rayon; #[cfg(feature = "rustc-rayon")] diff --git a/src/rayon/map.rs b/src/rayon/map.rs index a1f3588b..d5325f2e 100644 --- a/src/rayon/map.rs +++ b/src/rayon/map.rs @@ -3,8 +3,6 @@ //! You will rarely need to interact with this module directly unless you need to name one of the //! iterator types. -#![cfg_attr(docsrs, doc(cfg(feature = "rayon")))] - use super::collect; use rayon::iter::plumbing::{Consumer, ProducerCallback, UnindexedConsumer}; use rayon::prelude::*; diff --git a/src/rayon/set.rs b/src/rayon/set.rs index 9b8abadf..0fc478ef 100644 --- a/src/rayon/set.rs +++ b/src/rayon/set.rs @@ -3,8 +3,6 @@ //! You will rarely need to interact with this module directly unless you need to name one of the //! iterator types. -#![cfg_attr(docsrs, doc(cfg(feature = "rayon")))] - use super::collect; use rayon::iter::plumbing::{Consumer, ProducerCallback, UnindexedConsumer}; use rayon::prelude::*; @@ -22,7 +20,6 @@ use crate::IndexSet; type Bucket = crate::Bucket; -#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))] impl IntoParallelIterator for IndexSet where T: Send, @@ -37,7 +34,6 @@ where } } -#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))] impl IntoParallelIterator for Box> where T: Send, @@ -80,7 +76,6 @@ impl IndexedParallelIterator for IntoParIter { indexed_parallel_iterator_methods!(Bucket::key); } -#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))] impl<'a, T, S> IntoParallelIterator for &'a IndexSet where T: Sync, @@ -95,7 +90,6 @@ where } } -#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))] impl<'a, T> IntoParallelIterator for &'a Slice where T: Sync, @@ -144,7 +138,6 @@ impl IndexedParallelIterator for ParIter<'_, T> { indexed_parallel_iterator_methods!(Bucket::key_ref); } -#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))] impl<'a, T, S> ParallelDrainRange for &'a mut IndexSet where T: Send, @@ -585,7 +578,6 @@ where } } -#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))] impl FromParallelIterator for IndexSet where T: Eq + Hash + Send, @@ -605,7 +597,6 @@ where } } -#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))] impl ParallelExtend for IndexSet where T: Eq + Hash + Send, @@ -621,7 +612,6 @@ where } } -#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))] impl<'a, T: 'a, S> ParallelExtend<&'a T> for IndexSet where T: Copy + Eq + Hash + Send + Sync, diff --git a/src/serde.rs b/src/serde.rs index 6e55d206..37de3e3e 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -1,5 +1,3 @@ -#![cfg_attr(docsrs, doc(cfg(feature = "serde")))] - use serde::de::value::{MapDeserializer, SeqDeserializer}; use serde::de::{ Deserialize, Deserializer, Error, IntoDeserializer, MapAccess, SeqAccess, Visitor, diff --git a/src/serde_seq.rs b/src/serde_seq.rs index 30eab86c..b3ddd829 100644 --- a/src/serde_seq.rs +++ b/src/serde_seq.rs @@ -18,8 +18,6 @@ //! } //! ``` -#![cfg_attr(docsrs, doc(cfg(feature = "serde")))] - use serde::de::{Deserialize, Deserializer, SeqAccess, Visitor}; use serde::ser::{Serialize, Serializer}; From fb46d53e389fe27260a45ce405a84a2db5edd762 Mon Sep 17 00:00:00 2001 From: Richard Berry Date: Mon, 27 Feb 2023 14:22:24 +0000 Subject: [PATCH 112/236] Move `serde_seq` module from crate root to `map` --- src/lib.rs | 3 --- src/map.rs | 4 ++++ src/{ => map}/serde_seq.rs | 6 +++--- test-serde/src/lib.rs | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) rename src/{ => map}/serde_seq.rs (94%) diff --git a/src/lib.rs b/src/lib.rs index 74ac7ea7..fe78b062 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -116,9 +116,6 @@ mod mutable_keys; #[cfg(feature = "serde")] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] mod serde; -#[cfg(feature = "serde")] -#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] -pub mod serde_seq; mod util; pub mod map; diff --git a/src/map.rs b/src/map.rs index 3c86d6c8..ec5c3246 100644 --- a/src/map.rs +++ b/src/map.rs @@ -5,6 +5,10 @@ mod core; mod iter; mod slice; +#[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] +pub mod serde_seq; + #[cfg(test)] mod tests; diff --git a/src/serde_seq.rs b/src/map/serde_seq.rs similarity index 94% rename from src/serde_seq.rs rename to src/map/serde_seq.rs index b3ddd829..d7cbc248 100644 --- a/src/serde_seq.rs +++ b/src/map/serde_seq.rs @@ -12,7 +12,7 @@ //! # use serde_derive::{Deserialize, Serialize}; //! #[derive(Deserialize, Serialize)] //! struct Data { -//! #[serde(with = "indexmap::serde_seq")] +//! #[serde(with = "indexmap::map::serde_seq")] //! map: IndexMap, //! // ... //! } @@ -68,7 +68,7 @@ where /// # use serde_derive::Serialize; /// #[derive(Serialize)] /// struct Data { -/// #[serde(serialize_with = "indexmap::serde_seq::serialize")] +/// #[serde(serialize_with = "indexmap::map::serde_seq::serialize")] /// map: IndexMap, /// // ... /// } @@ -122,7 +122,7 @@ where /// # use serde_derive::Deserialize; /// #[derive(Deserialize)] /// struct Data { -/// #[serde(deserialize_with = "indexmap::serde_seq::deserialize")] +/// #[serde(deserialize_with = "indexmap::map::serde_seq::deserialize")] /// map: IndexMap, /// // ... /// } diff --git a/test-serde/src/lib.rs b/test-serde/src/lib.rs index b78a752f..553e0c77 100644 --- a/test-serde/src/lib.rs +++ b/test-serde/src/lib.rs @@ -77,7 +77,7 @@ fn test_serde_seq_map() { #[derive(Debug, Deserialize, Serialize)] #[serde(transparent)] struct SeqIndexMap { - #[serde(with = "indexmap::serde_seq")] + #[serde(with = "indexmap::map::serde_seq")] map: IndexMap, } From d996ac3819cd52e6e1e7a10153587e9610653204 Mon Sep 17 00:00:00 2001 From: Richard Berry Date: Tue, 28 Feb 2023 10:53:58 +0000 Subject: [PATCH 113/236] Fix docs.rs metadata --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a3d73c01..e4be89e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ no-dev-version = true tag-name = "{{version}}" [package.metadata.docs.rs] -all-features = ["arbitrary", "quickcheck", "serde", "rayon"] +features = ["arbitrary", "quickcheck", "serde", "rayon"] rustdoc-args = ["--cfg", "docsrs"] [workspace] From 42b03b4963ef8ad39f5312e515d297d6a7495da6 Mon Sep 17 00:00:00 2001 From: Richard Berry Date: Tue, 28 Feb 2023 10:59:04 +0000 Subject: [PATCH 114/236] Fix broken docstring links --- src/lib.rs | 2 +- src/map.rs | 1 - src/map/serde_seq.rs | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fe78b062..a574865e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,7 +39,7 @@ //! * `serde`: Adds implementations for [`Serialize`] and [`Deserialize`] //! to [`IndexMap`] and [`IndexSet`]. Alternative implementations for //! (de)serializing [`IndexMap`] as an ordered sequence are available in the -//! [`serde_seq`] module. +//! [`map::serde_seq`] module. //! //! _Note: only the `std` feature is enabled by default._ //! diff --git a/src/map.rs b/src/map.rs index ec5c3246..c3b7e0bb 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1118,7 +1118,6 @@ where #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - impl From<[(K, V); N]> for IndexMap where K: Hash + Eq, diff --git a/src/map/serde_seq.rs b/src/map/serde_seq.rs index d7cbc248..f10aa578 100644 --- a/src/map/serde_seq.rs +++ b/src/map/serde_seq.rs @@ -31,7 +31,7 @@ use crate::IndexMap; /// Serializes a `map::Slice` as an ordered sequence. /// -/// This behaves like [`crate::serde_seq`] for `IndexMap`, serializing a sequence +/// This behaves like [`crate::map::serde_seq`] for `IndexMap`, serializing a sequence /// of `(key, value)` pairs, rather than as a map that might not preserve order. impl Serialize for MapSlice where From eb8a6395a90bc148beaec3d438f3c77d67f21617 Mon Sep 17 00:00:00 2001 From: Richard Berry Date: Tue, 28 Feb 2023 11:20:01 +0000 Subject: [PATCH 115/236] Document `arbitrary` and `quickcheck` features --- src/arbitrary.rs | 2 ++ src/lib.rs | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/arbitrary.rs b/src/arbitrary.rs index 1347c8b5..7798438c 100644 --- a/src/arbitrary.rs +++ b/src/arbitrary.rs @@ -1,4 +1,5 @@ #[cfg(feature = "arbitrary")] +#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))] mod impl_arbitrary { use crate::{IndexMap, IndexSet}; use arbitrary::{Arbitrary, Result, Unstructured}; @@ -35,6 +36,7 @@ mod impl_arbitrary { } #[cfg(feature = "quickcheck")] +#[cfg_attr(docsrs, doc(cfg(feature = "quickcheck")))] mod impl_quickcheck { use crate::{IndexMap, IndexSet}; use alloc::boxed::Box; diff --git a/src/lib.rs b/src/lib.rs index a574865e..77cb4433 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,10 @@ //! to [`IndexMap`] and [`IndexSet`]. Alternative implementations for //! (de)serializing [`IndexMap`] as an ordered sequence are available in the //! [`map::serde_seq`] module. +//! * `arbitrary`: Adds implementations for the [`arbitrary::Arbitrary`] trait +//! to [`IndexMap`] and [`IndexSet`]. +//! * `quickcheck`: Adds implementations for the [`quickcheck::Arbitrary`] trait +//! to [`IndexMap`] and [`IndexSet`]. //! //! _Note: only the `std` feature is enabled by default._ //! @@ -47,6 +51,8 @@ //! [`no_std`]: #no-standard-library-targets //! [`Serialize`]: `::serde::Serialize` //! [`Deserialize`]: `::serde::Deserialize` +//! [`arbitrary::Arbitrary`]: `::arbitrary::Arbitrary` +//! [`quickcheck::Arbitrary`]: `::quickcheck::Arbitrary` //! //! ### Alternate Hashers //! From 8f26bbc053be0946074d625ffe2ae94a7ea54e2f Mon Sep 17 00:00:00 2001 From: Richard Berry Date: Tue, 28 Feb 2023 11:29:18 +0000 Subject: [PATCH 116/236] Add note on moved `serde_seq` module to RELEASES --- RELEASES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index 99a527a2..5f83652f 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -28,6 +28,9 @@ - The `hashbrown` dependency has been updated to version 0.13. + - The `serde_seq` module has been moved from the crate root to below the + `map` module. + - 1.9.2 - `IndexMap` and `IndexSet` both implement `arbitrary::Arbitrary<'_>` and From 11ac52c3c828a42d69c5fb3248198511836bfd2f Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 24 Mar 2023 16:52:51 -0700 Subject: [PATCH 117/236] [1.x] Update rustc-rayon to 0.5, and release 1.9.3 (cherry picked from commit 861fad73de14b7f08d2dc56ed83607aef621b42e) --- Cargo.toml | 2 +- RELEASES.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e4be89e5..6dcf64eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ rayon = { version = "1.5.3", optional = true } # Internal feature, only used when building as part of rustc, # not part of the stable interface of this crate. -rustc-rayon = { version = "0.4", optional = true } +rustc-rayon = { package = "rustc-rayon", version = "0.5", optional = true } [dependencies.hashbrown] version = "0.13" diff --git a/RELEASES.md b/RELEASES.md index 5f83652f..424de461 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -31,6 +31,10 @@ - The `serde_seq` module has been moved from the crate root to below the `map` module. +- 1.9.3 + + - Bump the `rustc-rayon` dependency, for compiler use only. + - 1.9.2 - `IndexMap` and `IndexSet` both implement `arbitrary::Arbitrary<'_>` and From 704bf5ade8880635c7e5fca16f34a632fb00e825 Mon Sep 17 00:00:00 2001 From: Konrad Borowski Date: Tue, 30 May 2023 18:39:34 +0200 Subject: [PATCH 118/236] Implement Default for iterators This matches Default implementations for HashMap and HashSet introduced in Rust 1.70. --- src/map/iter.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++++ src/map/tests.rs | 20 +++++++++++++++++ src/set/iter.rs | 14 ++++++++++++ src/set/tests.rs | 13 +++++++++++ 4 files changed, 105 insertions(+) diff --git a/src/map/iter.rs b/src/map/iter.rs index 6cd933e9..db6e140d 100644 --- a/src/map/iter.rs +++ b/src/map/iter.rs @@ -89,6 +89,12 @@ impl fmt::Debug for Iter<'_, K, V> { } } +impl Default for Iter<'_, K, V> { + fn default() -> Self { + Self { iter: [].iter() } + } +} + /// A mutable iterator over the entries of a `IndexMap`. /// /// This `struct` is created by the [`iter_mut`] method on [`IndexMap`]. See its @@ -145,6 +151,14 @@ impl fmt::Debug for IterMut<'_, K, V> { } } +impl Default for IterMut<'_, K, V> { + fn default() -> Self { + Self { + iter: [].iter_mut(), + } + } +} + /// An owning iterator over the entries of a `IndexMap`. /// /// This `struct` is created by the [`into_iter`] method on [`IndexMap`] @@ -199,6 +213,14 @@ impl fmt::Debug for IntoIter { } } +impl Default for IntoIter { + fn default() -> Self { + Self { + iter: Vec::new().into_iter(), + } + } +} + /// A draining iterator over the entries of a `IndexMap`. /// /// This `struct` is created by the [`drain`] method on [`IndexMap`]. See its @@ -298,6 +320,12 @@ impl fmt::Debug for Keys<'_, K, V> { } } +impl Default for Keys<'_, K, V> { + fn default() -> Self { + Self { iter: [].iter() } + } +} + /// An owning iterator over the keys of a `IndexMap`. /// /// This `struct` is created by the [`into_keys`] method on [`IndexMap`]. @@ -342,6 +370,14 @@ impl fmt::Debug for IntoKeys { } } +impl Default for IntoKeys { + fn default() -> Self { + Self { + iter: Vec::new().into_iter(), + } + } +} + /// An iterator over the values of a `IndexMap`. /// /// This `struct` is created by the [`values`] method on [`IndexMap`]. See its @@ -394,6 +430,12 @@ impl fmt::Debug for Values<'_, K, V> { } } +impl Default for Values<'_, K, V> { + fn default() -> Self { + Self { iter: [].iter() } + } +} + /// A mutable iterator over the values of a `IndexMap`. /// /// This `struct` is created by the [`values_mut`] method on [`IndexMap`]. See its @@ -438,6 +480,14 @@ impl fmt::Debug for ValuesMut<'_, K, V> { } } +impl Default for ValuesMut<'_, K, V> { + fn default() -> Self { + Self { + iter: [].iter_mut(), + } + } +} + /// An owning iterator over the values of a `IndexMap`. /// /// This `struct` is created by the [`into_values`] method on [`IndexMap`]. @@ -481,3 +531,11 @@ impl fmt::Debug for IntoValues { f.debug_list().entries(iter).finish() } } + +impl Default for IntoValues { + fn default() -> Self { + Self { + iter: Vec::new().into_iter(), + } + } +} diff --git a/src/map/tests.rs b/src/map/tests.rs index 423e3f3d..f273d716 100644 --- a/src/map/tests.rs +++ b/src/map/tests.rs @@ -427,3 +427,23 @@ fn from_array() { assert_eq!(map, expected) } + +#[test] +fn iter_default() { + struct K; + struct V; + fn assert_default() + where + T: Default + Iterator, + { + assert!(T::default().next().is_none()); + } + assert_default::>(); + assert_default::>(); + assert_default::>(); + assert_default::>(); + assert_default::>(); + assert_default::>(); + assert_default::>(); + assert_default::>(); +} diff --git a/src/set/iter.rs b/src/set/iter.rs index 24b3e86c..828756da 100644 --- a/src/set/iter.rs +++ b/src/set/iter.rs @@ -80,6 +80,12 @@ impl fmt::Debug for Iter<'_, T> { } } +impl Default for Iter<'_, T> { + fn default() -> Self { + Self { iter: [].iter() } + } +} + /// An owning iterator over the items of a `IndexSet`. /// /// This `struct` is created by the [`into_iter`] method on [`IndexSet`] @@ -129,6 +135,14 @@ impl fmt::Debug for IntoIter { } } +impl Default for IntoIter { + fn default() -> Self { + Self { + iter: Vec::new().into_iter(), + } + } +} + /// A draining iterator over the items of a `IndexSet`. /// /// This `struct` is created by the [`drain`] method on [`IndexSet`]. diff --git a/src/set/tests.rs b/src/set/tests.rs index 39e110f4..44f8ed84 100644 --- a/src/set/tests.rs +++ b/src/set/tests.rs @@ -530,3 +530,16 @@ fn from_array() { assert_eq!(set1, set2); } + +#[test] +fn iter_default() { + struct Item; + fn assert_default() + where + T: Default + Iterator, + { + assert!(T::default().next().is_none()); + } + assert_default::>(); + assert_default::>(); +} From c94ed9a0357c878cfba2ed6476ded2d7b3eb4d23 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Tue, 6 Jun 2023 12:59:16 +0200 Subject: [PATCH 119/236] Upgrade to hashbrown 0.14 (MSRV 1.64) --- .github/workflows/ci.yml | 4 ++-- Cargo.toml | 4 ++-- README.md | 2 +- RELEASES.md | 2 +- src/lib.rs | 2 +- src/map/core/raw.rs | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46086a70..ed3dec1d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: include: - - rust: 1.61.0 # MSRV + - rust: 1.64.0 # MSRV features: - rust: stable features: arbitrary @@ -59,7 +59,7 @@ jobs: strategy: matrix: include: - - rust: 1.61.0 + - rust: 1.64.0 target: thumbv6m-none-eabi - rust: stable target: thumbv6m-none-eabi diff --git a/Cargo.toml b/Cargo.toml index 6dcf64eb..22aa12ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ license = "Apache-2.0 OR MIT" description = "A hash table with consistent order and fast iteration." keywords = ["hashmap", "no_std"] categories = ["data-structures", "no-std"] -rust-version = "1.61" +rust-version = "1.64" [lib] bench = false @@ -25,7 +25,7 @@ rayon = { version = "1.5.3", optional = true } rustc-rayon = { package = "rustc-rayon", version = "0.5", optional = true } [dependencies.hashbrown] -version = "0.13" +version = "0.14" default-features = false features = ["raw"] diff --git a/README.md b/README.md index 95ceef5a..c8736019 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![build status](https://github.com/bluss/indexmap/workflows/Continuous%20integration/badge.svg?branch=master)](https://github.com/bluss/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.61%2B-orange.svg)](https://img.shields.io/badge/rust-1.61%2B-orange.svg) +[![rustc](https://img.shields.io/badge/rust-1.64%2B-orange.svg)](https://img.shields.io/badge/rust-1.64%2B-orange.svg) A pure-Rust hash table which preserves (in a limited sense) insertion order. diff --git a/RELEASES.md b/RELEASES.md index 424de461..3ceb99e9 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,6 +1,6 @@ - 2.0.0 (pending) - - **MSRV**: Rust 1.61.0 or later is now required. + - **MSRV**: Rust 1.64.0 or later is now required. - The `"std"` feature is no longer auto-detected. It is included in the default feature set, or else can be enabled like any other Cargo feature. diff --git a/src/lib.rs b/src/lib.rs index 77cb4433..2faf49e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,7 +81,7 @@ //! //! ### Rust Version //! -//! This version of indexmap requires Rust 1.61 or later. +//! This version of indexmap requires Rust 1.64 or later. //! //! The indexmap 2.x release series will use a carefully considered version //! upgrade policy, where in a later 2.x version, we will raise the minimum diff --git a/src/map/core/raw.rs b/src/map/core/raw.rs index c716fd9b..9085fedc 100644 --- a/src/map/core/raw.rs +++ b/src/map/core/raw.rs @@ -172,7 +172,7 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { // SAFETY: This is safe because it can only happen once (self is consumed) // and map.indices have not been modified since entry construction let index = unsafe { self.map.indices.remove(self.raw_bucket) }; - self.map.swap_remove_finish(index) + self.map.swap_remove_finish(index.0) } /// Remove and return the key, value pair stored in the map for this entry @@ -186,6 +186,6 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { // SAFETY: This is safe because it can only happen once (self is consumed) // and map.indices have not been modified since entry construction let index = unsafe { self.map.indices.remove(self.raw_bucket) }; - self.map.shift_remove_finish(index) + self.map.shift_remove_finish(index.0) } } From ec1fbce8315649ba5f28a587486913c1bba26c64 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Tue, 6 Jun 2023 17:53:29 +0200 Subject: [PATCH 120/236] Address review Co-Authored-By: Josh Stone --- RELEASES.md | 2 +- src/map/core/raw.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 3ceb99e9..90887a67 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -26,7 +26,7 @@ However, exactness only applies to the direct capacity for items, while the raw hash table still follows its own rules for capacity and load factor. - - The `hashbrown` dependency has been updated to version 0.13. + - The `hashbrown` dependency has been updated to version 0.14. - The `serde_seq` module has been moved from the crate root to below the `map` module. diff --git a/src/map/core/raw.rs b/src/map/core/raw.rs index 9085fedc..dee6363d 100644 --- a/src/map/core/raw.rs +++ b/src/map/core/raw.rs @@ -171,8 +171,8 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { pub fn swap_remove_entry(self) -> (K, V) { // SAFETY: This is safe because it can only happen once (self is consumed) // and map.indices have not been modified since entry construction - let index = unsafe { self.map.indices.remove(self.raw_bucket) }; - self.map.swap_remove_finish(index.0) + let (index, _slot) = unsafe { self.map.indices.remove(self.raw_bucket) }; + self.map.swap_remove_finish(index) } /// Remove and return the key, value pair stored in the map for this entry @@ -185,7 +185,7 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { pub fn shift_remove_entry(self) -> (K, V) { // SAFETY: This is safe because it can only happen once (self is consumed) // and map.indices have not been modified since entry construction - let index = unsafe { self.map.indices.remove(self.raw_bucket) }; - self.map.shift_remove_finish(index.0) + let (index, _slot) = unsafe { self.map.indices.remove(self.raw_bucket) }; + self.map.shift_remove_finish(index) } } From c37dae6bcb2fc2c1f45b1e6fd924f92685acd8b3 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 6 Jun 2023 12:41:54 -0700 Subject: [PATCH 121/236] Use hashbrown's new single-lookup insertion With `find_or_find_insert_slot` and `insert_in_slot`, we can avoid an extra insertion lookup after checking for existence. However, that also reserves space for a new entry *before* checking existence, so it's a little biased towards new items. For that reason, we're not using this for map `entry` or set `replace`, as both imply a little more weight on the expectation that it may already exist. --- src/map/core.rs | 30 +++++++++++++++++------------- src/map/core/raw.rs | 28 +++++++++++++++++++++++++++- src/set.rs | 12 ++---------- 3 files changed, 46 insertions(+), 24 deletions(-) diff --git a/src/map/core.rs b/src/map/core.rs index 908d1e50..c918deac 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -275,18 +275,14 @@ impl IndexMapCore { } } - /// Append a key-value pair, *without* checking whether it already exists, - /// and return the pair's new index. - fn push(&mut self, hash: HashValue, key: K, value: V) -> usize { - let i = self.entries.len(); - self.indices.insert(hash.get(), i, get_hash(&self.entries)); - if i == self.entries.capacity() { + /// Append a key-value pair to `entries`, *without* checking whether it already exists. + fn push_entry(&mut self, 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::push` just double it. self.reserve_entries(1); } self.entries.push(Bucket { hash, key, value }); - i } /// Return the index in `entries` where an equivalent key can be found @@ -302,9 +298,13 @@ impl IndexMapCore { where K: Eq, { - match self.get_index_of(hash, &key) { - Some(i) => (i, Some(mem::replace(&mut self.entries[i].value, value))), - None => (self.push(hash, key, value), None), + match self.find_or_insert(hash, &key) { + Ok(i) => (i, Some(mem::replace(&mut self.entries[i].value, value))), + Err(i) => { + debug_assert_eq!(i, self.entries.len()); + self.push_entry(hash, key, value); + (i, None) + } } } @@ -712,14 +712,18 @@ impl<'a, K, V> VacantEntry<'a, K, V> { /// Return the index where the key-value pair will be inserted. pub fn index(&self) -> usize { - self.map.len() + self.map.indices.len() } /// 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.map.push(self.hash, self.key, value); - &mut self.map.entries[i].value + 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); + &mut map.entries[i].value } } diff --git a/src/map/core/raw.rs b/src/map/core/raw.rs index dee6363d..332770cc 100644 --- a/src/map/core/raw.rs +++ b/src/map/core/raw.rs @@ -2,7 +2,7 @@ //! This module encapsulates the `unsafe` access to `hashbrown::raw::RawTable`, //! mostly in dealing with its bucket "pointers". -use super::{equivalent, Bucket, Entry, HashValue, IndexMapCore, VacantEntry}; +use super::{equivalent, get_hash, Bucket, Entry, HashValue, IndexMapCore, VacantEntry}; use core::fmt; use core::mem::replace; use hashbrown::raw::RawTable; @@ -48,6 +48,32 @@ impl IndexMapCore { } } + /// Search for a key in the table and return `Ok(entry_index)` if found. + /// Otherwise, insert the key and return `Err(new_index)`. + /// + /// Note that hashbrown may resize the table to reserve space for insertion, + /// even before checking if it's already present, so this is somewhat biased + /// towards new items. + pub(crate) fn find_or_insert(&mut self, hash: HashValue, key: &K) -> Result + where + K: Eq, + { + let hash = hash.get(); + let eq = equivalent(key, &self.entries); + let hasher = get_hash(&self.entries); + // SAFETY: We're not mutating between find and read/insert. + unsafe { + match self.indices.find_or_find_insert_slot(hash, eq, hasher) { + Ok(raw_bucket) => Ok(*raw_bucket.as_ref()), + Err(slot) => { + let index = self.indices.len(); + self.indices.insert_in_slot(hash, slot, index); + Err(index) + } + } + } + } + pub(crate) fn entry(&mut self, hash: HashValue, key: K) -> Entry<'_, K, V> where K: Eq, diff --git a/src/set.rs b/src/set.rs index 035d5d73..8e107594 100644 --- a/src/set.rs +++ b/src/set.rs @@ -335,16 +335,8 @@ where /// /// Computes in **O(1)** time (amortized average). pub fn insert_full(&mut self, value: T) -> (usize, bool) { - use super::map::Entry::*; - - match self.map.entry(value) { - Occupied(e) => (e.index(), false), - Vacant(e) => { - let index = e.index(); - e.insert(()); - (index, true) - } - } + let (index, existing) = self.map.insert_full(value, ()); + (index, existing.is_none()) } /// Return an iterator over the values that are in `self` but not `other`. From 6d83bc1902b95758d98ea973778d8fc4b4a599a2 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 6 Jun 2023 15:49:05 -0700 Subject: [PATCH 122/236] pub use equivalent::Equivalent; --- Cargo.toml | 2 ++ src/equivalent.rs | 27 --------------------------- src/lib.rs | 3 +-- src/map.rs | 3 +-- src/map/core.rs | 3 +-- 5 files changed, 5 insertions(+), 33 deletions(-) delete mode 100644 src/equivalent.rs diff --git a/Cargo.toml b/Cargo.toml index 22aa12ae..c2443c48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,8 @@ rust-version = "1.64" bench = false [dependencies] +equivalent = { version = "1.0", default-features = false } + arbitrary = { version = "1.0", optional = true, default-features = false } quickcheck = { version = "1.0", optional = true, default-features = false } serde = { version = "1.0", optional = true, default-features = false } diff --git a/src/equivalent.rs b/src/equivalent.rs deleted file mode 100644 index ad6635ff..00000000 --- a/src/equivalent.rs +++ /dev/null @@ -1,27 +0,0 @@ -use core::borrow::Borrow; - -/// Key equivalence trait. -/// -/// This trait allows hash table lookup to be customized. -/// It has one blanket implementation that uses the regular `Borrow` solution, -/// just like `HashMap` and `BTreeMap` do, so that you can pass `&str` to lookup -/// into a map with `String` keys and so on. -/// -/// # Contract -/// -/// The implementor **must** hash like `K`, if it is hashable. -pub trait Equivalent { - /// Compare self to `key` and return `true` if they are equal. - fn equivalent(&self, key: &K) -> bool; -} - -impl Equivalent for Q -where - Q: Eq, - K: Borrow, -{ - #[inline] - fn equivalent(&self, key: &K) -> bool { - *self == *key.borrow() - } -} diff --git a/src/lib.rs b/src/lib.rs index 2faf49e2..8939d261 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -117,7 +117,6 @@ use alloc::vec::{self, Vec}; mod arbitrary; #[macro_use] mod macros; -mod equivalent; mod mutable_keys; #[cfg(feature = "serde")] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] @@ -136,9 +135,9 @@ mod rayon; #[cfg(feature = "rustc-rayon")] mod rustc; -pub use crate::equivalent::Equivalent; pub use crate::map::IndexMap; pub use crate::set::IndexSet; +pub use equivalent::Equivalent; // shared private items diff --git a/src/map.rs b/src/map.rs index c3b7e0bb..cd03ae8a 100644 --- a/src/map.rs +++ b/src/map.rs @@ -33,9 +33,8 @@ use alloc::vec::Vec; use std::collections::hash_map::RandomState; use self::core::IndexMapCore; -use crate::equivalent::Equivalent; use crate::util::{third, try_simplify_range}; -use crate::{Bucket, Entries, HashValue, TryReserveError}; +use crate::{Bucket, Entries, Equivalent, HashValue, TryReserveError}; /// A hash table where the iteration order of the key-value pairs is independent /// of the hash values of the keys. diff --git a/src/map/core.rs b/src/map/core.rs index c918deac..3e392ace 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -17,9 +17,8 @@ use core::fmt; use core::mem; use core::ops::RangeBounds; -use crate::equivalent::Equivalent; use crate::util::simplify_range; -use crate::{Bucket, Entries, HashValue}; +use crate::{Bucket, Entries, Equivalent, HashValue}; /// Core of the map that does not depend on S pub(crate) struct IndexMapCore { From 677c60522815f53e83ab173c199772567e9c9007 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 7 Jun 2023 08:52:32 -0700 Subject: [PATCH 123/236] Add a relnote for Equivalent --- RELEASES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index 90887a67..fa460594 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -26,6 +26,9 @@ However, exactness only applies to the direct capacity for items, while the raw hash table still follows its own rules for capacity and load factor. + - The `Equivalent` trait is now re-exported from the `equivalent` crate, + intended as a common base to allow types to work with multiple map types. + - The `hashbrown` dependency has been updated to version 0.14. - The `serde_seq` module has been moved from the crate root to below the From d3ea28992194484dea671a19b808b62ed8efd5d1 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 23 Jun 2023 12:26:51 -0700 Subject: [PATCH 124/236] Document the lower-bound semantics of capacity --- src/map.rs | 5 +++++ src/set.rs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/map.rs b/src/map.rs index cd03ae8a..cb405caf 100644 --- a/src/map.rs +++ b/src/map.rs @@ -197,6 +197,11 @@ impl IndexMap { } } + /// Return the number of elements the map can hold without reallocating. + /// + /// This number is a lower bound; the map might be able to hold more, + /// but is guaranteed to be able to hold at least this many. + /// /// Computes in **O(1)** time. pub fn capacity(&self) -> usize { self.core.capacity() diff --git a/src/set.rs b/src/set.rs index 8e107594..dbee481f 100644 --- a/src/set.rs +++ b/src/set.rs @@ -176,6 +176,11 @@ impl IndexSet { } } + /// Return the number of elements the set can hold without reallocating. + /// + /// This number is a lower bound; the set might be able to hold more, + /// but is guaranteed to be able to hold at least this many. + /// /// Computes in **O(1)** time. pub fn capacity(&self) -> usize { self.map.capacity() From ad694fbb8c73f92e4316e436422dc80763a7ed03 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 23 Jun 2023 12:45:43 -0700 Subject: [PATCH 125/236] Release 2.0.0 --- Cargo.toml | 3 +-- RELEASES.md | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c2443c48..14877a91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,7 @@ [package] name = "indexmap" edition = "2021" -version = "2.0.0-pre" -publish = false +version = "2.0.0" documentation = "https://docs.rs/indexmap/" repository = "https://github.com/bluss/indexmap" license = "Apache-2.0 OR MIT" diff --git a/RELEASES.md b/RELEASES.md index fa460594..1fe5ad89 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,4 +1,4 @@ -- 2.0.0 (pending) +- 2.0.0 - **MSRV**: Rust 1.64.0 or later is now required. From 73b4b53b8c232895147ebc656accd55ed5102104 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 21 Jul 2023 18:26:55 -0700 Subject: [PATCH 126/236] Upgrade to itertools 0.11 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 14877a91..a3c21eee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ default-features = false features = ["raw"] [dev-dependencies] -itertools = "0.10" +itertools = "0.11" rand = {version = "0.8", features = ["small_rng"] } quickcheck = { version = "1.0", default-features = false } fnv = "1.0" From 836084780e096c2c33e2b84be22ca2f2d1e71def Mon Sep 17 00:00:00 2001 From: daxpedda Date: Fri, 11 Aug 2023 16:44:46 +0200 Subject: [PATCH 127/236] Test `direct-minimal-versions` --- .github/workflows/ci.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed3dec1d..f5a5e2dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,3 +92,21 @@ jobs: with: components: miri - run: cargo miri test + + minimal-versions: + name: Check MSRV and minimal-versions + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.64.0 # MSRV + - uses: taiki-e/install-action@v2 + with: + tool: cargo-hack + - run: cargo +nightly hack generate-lockfile --remove-dev-deps -Z direct-minimal-versions + - name: Build + run: cargo build --verbose --all-features From 77c58aaab83f268f6934ebe652484f9813854671 Mon Sep 17 00:00:00 2001 From: ynn Date: Wed, 16 Aug 2023 09:11:08 +0900 Subject: [PATCH 128/236] Improve explanation about `IndexSet`'s complexity. --- src/set.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/set.rs b/src/set.rs index dbee481f..811f462e 100644 --- a/src/set.rs +++ b/src/set.rs @@ -56,6 +56,11 @@ type Bucket = super::Bucket; /// `0..self.len()`. For example, the method `.get_full` looks up the index for /// a value, and the method `.get_index` looks up the value by index. /// +/// # Complexity +/// +/// Internally, `IndexSet` just holds an [`IndexMap`](IndexMap). Thus the complexity +/// of the two are the same for most methods. +/// /// # Examples /// /// ``` @@ -420,6 +425,8 @@ where } /// Return item index, if it exists in the set + /// + /// Computes in **O(1)** time (average). pub fn get_index_of(&self, value: &Q) -> Option where Q: Hash + Equivalent, From 8e03753066e75908694f0240198911b8fc75db69 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 6 Sep 2023 16:44:21 -0700 Subject: [PATCH 129/236] Use `RawTable::get_many_mut` for safe `swap_indices` --- src/map/core.rs | 20 ++++++++++++++++++++ src/map/core/raw.rs | 23 ----------------------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/map/core.rs b/src/map/core.rs index 3e392ace..4a78035c 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -410,6 +410,26 @@ impl IndexMapCore { } } + pub(crate) fn swap_indices(&mut self, a: usize, b: usize) { + // If they're equal and in-bounds, there's nothing to do. + if a == b && a < self.entries.len() { + return; + } + + // We'll get a "nice" bounds-check from indexing `self.entries`, + // and then we expect to find it in the table as well. + let [ref_a, ref_b] = self + .indices + .get_many_mut( + [self.entries[a].hash.get(), self.entries[b].hash.get()], + move |i, &x| if i == 0 { x == a } else { x == b }, + ) + .expect("indices not found"); + + mem::swap(ref_a, ref_b); + self.entries.swap(a, b); + } + /// Remove an entry by swapping it with the last pub(crate) fn swap_remove_full(&mut self, hash: HashValue, key: &Q) -> Option<(usize, K, V)> where diff --git a/src/map/core/raw.rs b/src/map/core/raw.rs index 332770cc..be71c9cf 100644 --- a/src/map/core/raw.rs +++ b/src/map/core/raw.rs @@ -100,29 +100,6 @@ impl IndexMapCore { // only the item references that are appropriately bound to `&mut self`. unsafe { self.indices.iter().map(|bucket| bucket.as_mut()) } } - - /// Return the raw bucket for the given index - fn find_index(&self, index: usize) -> RawBucket { - // We'll get a "nice" bounds-check from indexing `self.entries`, - // and then we expect to find it in the table as well. - let hash = self.entries[index].hash.get(); - self.indices - .find(hash, move |&i| i == index) - .expect("index not found") - } - - pub(crate) fn swap_indices(&mut self, a: usize, b: usize) { - // SAFETY: Can't take two `get_mut` references from one table, so we - // must use raw buckets to do the swap. This is still safe because we - // are locally sure they won't dangle, and we write them individually. - unsafe { - let raw_bucket_a = self.find_index(a); - let raw_bucket_b = self.find_index(b); - *raw_bucket_a.as_mut() = b; - *raw_bucket_b.as_mut() = a; - } - self.entries.swap(a, b); - } } /// A view into an occupied entry in a `IndexMap`. From 9dcae7688e96fb29d4e80b40c32318613d5d3b5b Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 27 Sep 2023 09:11:29 -0700 Subject: [PATCH 130/236] Release 2.0.1 with `rust-version = "1.63"` --- .github/workflows/ci.yml | 8 ++++++++ Cargo.toml | 4 ++-- README.md | 2 +- RELEASES.md | 5 +++++ src/lib.rs | 2 +- 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f5a5e2dc..9c10eacf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -110,3 +110,11 @@ jobs: - run: cargo +nightly hack generate-lockfile --remove-dev-deps -Z direct-minimal-versions - name: Build run: cargo build --verbose --all-features + + # See hashbrown#457: it can actually work with 1.63.0 + relaxed-msrv: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@1.63.0 + - run: cargo test --verbose --ignore-rust-version diff --git a/Cargo.toml b/Cargo.toml index a3c21eee..2664a391 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "indexmap" edition = "2021" -version = "2.0.0" +version = "2.0.1" documentation = "https://docs.rs/indexmap/" repository = "https://github.com/bluss/indexmap" license = "Apache-2.0 OR MIT" description = "A hash table with consistent order and fast iteration." keywords = ["hashmap", "no_std"] categories = ["data-structures", "no-std"] -rust-version = "1.64" +rust-version = "1.63" [lib] bench = false diff --git a/README.md b/README.md index c8736019..bf869323 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![build status](https://github.com/bluss/indexmap/workflows/Continuous%20integration/badge.svg?branch=master)](https://github.com/bluss/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.64%2B-orange.svg)](https://img.shields.io/badge/rust-1.64%2B-orange.svg) +[![rustc](https://img.shields.io/badge/rust-1.63%2B-orange.svg)](https://img.shields.io/badge/rust-1.63%2B-orange.svg) A pure-Rust hash table which preserves (in a limited sense) insertion order. diff --git a/RELEASES.md b/RELEASES.md index 1fe5ad89..2f564a4e 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,8 @@ +- 2.0.1 + + - **MSRV**: Rust 1.63.0 is now supported as well, pending publication of + `hashbrown`'s relaxed MSRV (or use cargo `--ignore-rust-version`). + - 2.0.0 - **MSRV**: Rust 1.64.0 or later is now required. diff --git a/src/lib.rs b/src/lib.rs index 8939d261..5e427843 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,7 +81,7 @@ //! //! ### Rust Version //! -//! This version of indexmap requires Rust 1.64 or later. +//! This version of indexmap requires Rust 1.63 or later. //! //! The indexmap 2.x release series will use a carefully considered version //! upgrade policy, where in a later 2.x version, we will raise the minimum From 9de727dafa8e025b3b2b100a8a2a918019d366a6 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 29 Sep 2023 09:19:08 -0700 Subject: [PATCH 131/236] Release 2.0.2 with complete MSRV 1.63 --- .github/workflows/ci.yml | 14 +++----------- Cargo.toml | 4 ++-- RELEASES.md | 5 +++++ 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c10eacf..d8203430 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: include: - - rust: 1.64.0 # MSRV + - rust: 1.63.0 # MSRV features: - rust: stable features: arbitrary @@ -59,7 +59,7 @@ jobs: strategy: matrix: include: - - rust: 1.64.0 + - rust: 1.63.0 target: thumbv6m-none-eabi - rust: stable target: thumbv6m-none-eabi @@ -103,18 +103,10 @@ jobs: toolchain: nightly - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.64.0 # MSRV + toolchain: 1.63.0 # MSRV - uses: taiki-e/install-action@v2 with: tool: cargo-hack - run: cargo +nightly hack generate-lockfile --remove-dev-deps -Z direct-minimal-versions - name: Build run: cargo build --verbose --all-features - - # See hashbrown#457: it can actually work with 1.63.0 - relaxed-msrv: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@1.63.0 - - run: cargo test --verbose --ignore-rust-version diff --git a/Cargo.toml b/Cargo.toml index 2664a391..e57c7333 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "indexmap" edition = "2021" -version = "2.0.1" +version = "2.0.2" documentation = "https://docs.rs/indexmap/" repository = "https://github.com/bluss/indexmap" license = "Apache-2.0 OR MIT" @@ -26,7 +26,7 @@ rayon = { version = "1.5.3", optional = true } rustc-rayon = { package = "rustc-rayon", version = "0.5", optional = true } [dependencies.hashbrown] -version = "0.14" +version = "0.14.1" default-features = false features = ["raw"] diff --git a/RELEASES.md b/RELEASES.md index 2f564a4e..0964d198 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,8 @@ +- 2.0.2 + + - The `hashbrown` dependency has been updated to version 0.14.1 to + complete the support for Rust 1.63. + - 2.0.1 - **MSRV**: Rust 1.63.0 is now supported as well, pending publication of From 0187071f221579d300672f944d135a1da25b31cc Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 4 Oct 2023 15:54:02 -0700 Subject: [PATCH 132/236] Add `Slice::new` and `new_mut` for empty slices * `pub const fn map::Slice::new<'a>() -> &'a Self` * `pub fn map::Slice::new_mut<'a>() -> &'a mut Self` * `pub const fn set::Slice::new<'a>() -> &'a Self` In addition, the existing `len` and `is_empty` are now `const fn`. --- src/map/slice.rs | 16 +++++++++++++--- src/set/slice.rs | 11 ++++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/map/slice.rs b/src/map/slice.rs index 9fb876fd..e2e30c2f 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -27,7 +27,7 @@ pub struct Slice { // and reference lifetimes are bound together in function signatures. #[allow(unsafe_code)] impl Slice { - pub(super) fn from_slice(entries: &[Bucket]) -> &Self { + pub(super) const fn from_slice(entries: &[Bucket]) -> &Self { unsafe { &*(entries as *const [Bucket] as *const Self) } } @@ -49,15 +49,25 @@ impl Slice { self.into_boxed().into_vec() } + /// Returns an empty slice. + pub const fn new<'a>() -> &'a Self { + Self::from_slice(&[]) + } + + /// Returns an empty mutable slice. + pub fn new_mut<'a>() -> &'a mut Self { + Self::from_mut_slice(&mut []) + } + /// Return the number of key-value pairs in the map slice. #[inline] - pub fn len(&self) -> usize { + pub const fn len(&self) -> usize { self.entries.len() } /// Returns true if the map slice contains no elements. #[inline] - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.entries.is_empty() } diff --git a/src/set/slice.rs b/src/set/slice.rs index 608311d2..babac017 100644 --- a/src/set/slice.rs +++ b/src/set/slice.rs @@ -24,7 +24,7 @@ pub struct Slice { // and reference lifetimes are bound together in function signatures. #[allow(unsafe_code)] impl Slice { - pub(super) fn from_slice(entries: &[Bucket]) -> &Self { + pub(super) const fn from_slice(entries: &[Bucket]) -> &Self { unsafe { &*(entries as *const [Bucket] as *const Self) } } @@ -42,13 +42,18 @@ impl Slice { self.into_boxed().into_vec() } + /// Returns an empty slice. + pub const fn new<'a>() -> &'a Self { + Self::from_slice(&[]) + } + /// Return the number of elements in the set slice. - pub fn len(&self) -> usize { + pub const fn len(&self) -> usize { self.entries.len() } /// Returns true if the set slice contains no elements. - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.entries.is_empty() } From 348b42f3cde32ca416ad76e3ceb8bbe3090fd01e Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Tue, 24 Oct 2023 21:23:58 +0000 Subject: [PATCH 133/236] add binary_search and friends --- src/map.rs | 40 ++++++++++++++++++++++++++++++++++++ src/map/slice.rs | 53 ++++++++++++++++++++++++++++++++++++++++++++++++ src/set.rs | 40 ++++++++++++++++++++++++++++++++++++ src/set/slice.rs | 52 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 185 insertions(+) diff --git a/src/map.rs b/src/map.rs index cb405caf..af724940 100644 --- a/src/map.rs +++ b/src/map.rs @@ -794,6 +794,46 @@ where }); } + /// Search over a sorted map with a comparator function. + /// + /// Returns the position where that value is present, or the position where can be inserted to maintain the sort. + /// see [slice::binary_search_by] for more details. + /// **O(log(n))** + #[inline] + pub fn binary_search_by<'a, F>(&'a self, f: F) -> Result + where + F: FnMut(&'a K, &'a V) -> Ordering, + { + self.as_slice().binary_search_by(f) + } + + /// Search over a sorted map with a key extraction function. + /// + /// Returns the position where that value is present, or the position where can be inserted to maintain the sort. + /// see [slice::binary_search_by_key] for more details. + /// **O(log(n))** + #[inline] + pub fn binary_search_by_key<'a, B, F>(&'a self, b: &B, f: F) -> Result + where + F: FnMut(&'a K, &'a V) -> B, + B: Ord, + { + self.as_slice().binary_search_by_key(b, f) + } + + /// Returns the index of the partition point or a sorted map according to the given predicate + /// (the index of the first element of the second partition). + /// + /// see [slice::partition_point] for more details. + /// **O(log(n))** + #[must_use] + pub fn partition_point

(&self, pred: P) -> usize + where + P: FnMut(&K, &V) -> bool, + { + self.as_slice().partition_point(pred) + } + /// Reverses the order of the map’s key-value pairs in place. /// /// Computes in **O(n)** time and **O(1)** space. diff --git a/src/map/slice.rs b/src/map/slice.rs index 9fb876fd..ec3c914b 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -201,6 +201,59 @@ impl Slice { pub fn into_values(self: Box) -> IntoValues { IntoValues::new(self.into_entries()) } + + /// Search over a sorted map for a value. + /// + /// Returns the position where that value is present, or the position where can be inserted to maintain the sort. + /// see [slice::binary_search] for more details. + /// **O(log(n))**, which is notably less scalable than looking the value up in the map set is a slice from. + pub fn binary_search_keys(&self, x: &K) -> Result + where + K: Ord, + { + self.binary_search_by(|p, _| p.cmp(x)) + } + + /// Search over a sorted map with a comparator function. + /// + /// Returns the position where that value is present, or the position where can be inserted to maintain the sort. + /// see [slice::binary_search_by] for more details. + /// **O(log(n))** + #[inline] + pub fn binary_search_by<'a, F>(&'a self, mut f: F) -> Result + where + F: FnMut(&'a K, &'a V) -> Ordering, + { + self.entries.binary_search_by(move |a| f(&a.key, &a.value)) + } + + /// Search over a sorted map with a key extraction function. + /// + /// Returns the position where that value is present, or the position where can be inserted to maintain the sort. + /// see [slice::binary_search_by_key] for more details. + /// **O(log(n))** + #[inline] + pub fn binary_search_by_key<'a, B, F>(&'a self, b: &B, mut f: F) -> Result + where + F: FnMut(&'a K, &'a V) -> B, + B: Ord, + { + self.binary_search_by(|k, v| f(k, v).cmp(b)) + } + + /// Returns the index of the partition point or a sorted map according to the given predicate + /// (the index of the first element of the second partition). + /// + /// see [slice::partition_point] for more details. + /// **O(log(n))** + #[must_use] + pub fn partition_point

(&self, mut pred: P) -> usize + where + P: FnMut(&K, &V) -> bool, + { + self.entries + .partition_point(move |a| pred(&a.key, &a.value)) + } } impl<'a, K, V> IntoIterator for &'a Slice { diff --git a/src/set.rs b/src/set.rs index 811f462e..bfb0e528 100644 --- a/src/set.rs +++ b/src/set.rs @@ -688,6 +688,46 @@ where }); } + /// Search over a sorted set with a comparator function. + /// + /// Returns the position where that value is present, or the position where can be inserted to maintain the sort. + /// see [slice::binary_search_by] for more details. + /// **O(log(n))** + #[inline] + pub fn binary_search_by<'a, F>(&'a self, f: F) -> Result + where + F: FnMut(&'a T) -> Ordering, + { + self.as_slice().binary_search_by(f) + } + + /// Search over a sorted set with a key extraction function. + /// + /// Returns the position where that value is present, or the position where can be inserted to maintain the sort. + /// see [slice::binary_search_by_key] for more details. + /// **O(log(n))** + #[inline] + pub fn binary_search_by_key<'a, B, F>(&'a self, b: &B, f: F) -> Result + where + F: FnMut(&'a T) -> B, + B: Ord, + { + self.as_slice().binary_search_by_key(b, f) + } + + /// Returns the index of the partition point or a sorted set according to the given predicate + /// (the index of the first element of the second partition). + /// + /// see [slice::partition_point] for more details. + /// **O(log(n))** + #[must_use] + pub fn partition_point

(&self, pred: P) -> usize + where + P: FnMut(&T) -> bool, + { + self.as_slice().partition_point(pred) + } + /// Reverses the order of the set’s values in place. /// /// Computes in **O(n)** time and **O(1)** space. diff --git a/src/set/slice.rs b/src/set/slice.rs index 608311d2..9f2a2d53 100644 --- a/src/set/slice.rs +++ b/src/set/slice.rs @@ -109,6 +109,58 @@ impl Slice { pub fn iter(&self) -> Iter<'_, T> { Iter::new(&self.entries) } + + /// Search over a sorted set for a value. + /// + /// Returns the position where that value is present, or the position where can be inserted to maintain the sort. + /// see [slice::binary_search] for more details. + /// **O(log(n))**, which is notably less scalable than looking the value up in the set this is a slice from. + pub fn binary_search(&self, x: &T) -> Result + where + T: Ord, + { + self.binary_search_by(|p| p.cmp(x)) + } + + /// Search over a sorted set with a comparator function. + /// + /// Returns the position where that value is present, or the position where can be inserted to maintain the sort. + /// see [slice::binary_search_by] for more details. + /// **O(log(n))** + #[inline] + pub fn binary_search_by<'a, F>(&'a self, mut f: F) -> Result + where + F: FnMut(&'a T) -> Ordering, + { + self.entries.binary_search_by(move |a| f(&a.key)) + } + + /// Search over a sorted set with a key extraction function. + /// + /// Returns the position where that value is present, or the position where can be inserted to maintain the sort. + /// see [slice::binary_search_by_key] for more details. + /// **O(log(n))** + #[inline] + pub fn binary_search_by_key<'a, B, F>(&'a self, b: &B, mut f: F) -> Result + where + F: FnMut(&'a T) -> B, + B: Ord, + { + self.binary_search_by(|k| f(k).cmp(b)) + } + + /// Returns the index of the partition point or a sorted set according to the given predicate + /// (the index of the first element of the second partition). + /// + /// see [slice::partition_point] for more details. + /// **O(log(n))** + #[must_use] + pub fn partition_point

(&self, mut pred: P) -> usize + where + P: FnMut(&T) -> bool, + { + self.entries.partition_point(move |a| pred(&a.key)) + } } impl<'a, T> IntoIterator for &'a Slice { From d93534fa2937599e2eb6fa59daa55e2ba2705f55 Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Thu, 26 Oct 2023 19:50:29 +0000 Subject: [PATCH 134/236] add tests --- src/map/tests.rs | 221 +++++++++++++++++++++++++++++++++++++++++++++++ src/set/tests.rs | 139 +++++++++++++++++++++++++++++ 2 files changed, 360 insertions(+) diff --git a/src/map/tests.rs b/src/map/tests.rs index f273d716..3f213212 100644 --- a/src/map/tests.rs +++ b/src/map/tests.rs @@ -447,3 +447,224 @@ fn iter_default() { assert_default::>(); assert_default::>(); } + +#[test] +fn test_binary_search_by() { + // addaped from stds test for binary_search + let b: IndexMap<_, i32> = [] + .into_iter() + .enumerate() + .map(|(i, x)| (i + 100, x)) + .collect(); + assert_eq!(b.binary_search_by(|_, x| x.cmp(&5)), Err(0)); + + let b: IndexMap<_, i32> = [4] + .into_iter() + .enumerate() + .map(|(i, x)| (i + 100, x)) + .collect(); + assert_eq!(b.binary_search_by(|_, x| x.cmp(&3)), Err(0)); + assert_eq!(b.binary_search_by(|_, x| x.cmp(&4)), Ok(0)); + assert_eq!(b.binary_search_by(|_, x| x.cmp(&5)), Err(1)); + + let b: IndexMap<_, i32> = [1, 2, 4, 6, 8, 9] + .into_iter() + .enumerate() + .map(|(i, x)| (i + 100, x)) + .collect(); + assert_eq!(b.binary_search_by(|_, x| x.cmp(&5)), Err(3)); + assert_eq!(b.binary_search_by(|_, x| x.cmp(&6)), Ok(3)); + assert_eq!(b.binary_search_by(|_, x| x.cmp(&7)), Err(4)); + assert_eq!(b.binary_search_by(|_, x| x.cmp(&8)), Ok(4)); + + let b: IndexMap<_, i32> = [1, 2, 4, 5, 6, 8] + .into_iter() + .enumerate() + .map(|(i, x)| (i + 100, x)) + .collect(); + assert_eq!(b.binary_search_by(|_, x| x.cmp(&9)), Err(6)); + + let b: IndexMap<_, i32> = [1, 2, 4, 6, 7, 8, 9] + .into_iter() + .enumerate() + .map(|(i, x)| (i + 100, x)) + .collect(); + assert_eq!(b.binary_search_by(|_, x| x.cmp(&6)), Ok(3)); + assert_eq!(b.binary_search_by(|_, x| x.cmp(&5)), Err(3)); + assert_eq!(b.binary_search_by(|_, x| x.cmp(&8)), Ok(5)); + + let b: IndexMap<_, i32> = [1, 2, 4, 5, 6, 8, 9] + .into_iter() + .enumerate() + .map(|(i, x)| (i + 100, x)) + .collect(); + assert_eq!(b.binary_search_by(|_, x| x.cmp(&7)), Err(5)); + assert_eq!(b.binary_search_by(|_, x| x.cmp(&0)), Err(0)); + + let b: IndexMap<_, i32> = [1, 3, 3, 3, 7] + .into_iter() + .enumerate() + .map(|(i, x)| (i + 100, x)) + .collect(); + assert_eq!(b.binary_search_by(|_, x| x.cmp(&0)), Err(0)); + assert_eq!(b.binary_search_by(|_, x| x.cmp(&1)), Ok(0)); + assert_eq!(b.binary_search_by(|_, x| x.cmp(&2)), Err(1)); + assert!(match b.binary_search_by(|_, x| x.cmp(&3)) { + Ok(1..=3) => true, + _ => false, + }); + assert!(match b.binary_search_by(|_, x| x.cmp(&3)) { + Ok(1..=3) => true, + _ => false, + }); + assert_eq!(b.binary_search_by(|_, x| x.cmp(&4)), Err(4)); + assert_eq!(b.binary_search_by(|_, x| x.cmp(&5)), Err(4)); + assert_eq!(b.binary_search_by(|_, x| x.cmp(&6)), Err(4)); + assert_eq!(b.binary_search_by(|_, x| x.cmp(&7)), Ok(4)); + assert_eq!(b.binary_search_by(|_, x| x.cmp(&8)), Err(5)); +} + +#[test] +fn test_binary_search_by_key() { + // addaped from stds test for binary_search + let b: IndexMap<_, i32> = [] + .into_iter() + .enumerate() + .map(|(i, x)| (i + 100, x)) + .collect(); + assert_eq!(b.binary_search_by_key(&5, |_, &x| x), Err(0)); + + let b: IndexMap<_, i32> = [4] + .into_iter() + .enumerate() + .map(|(i, x)| (i + 100, x)) + .collect(); + assert_eq!(b.binary_search_by_key(&3, |_, &x| x), Err(0)); + assert_eq!(b.binary_search_by_key(&4, |_, &x| x), Ok(0)); + assert_eq!(b.binary_search_by_key(&5, |_, &x| x), Err(1)); + + let b: IndexMap<_, i32> = [1, 2, 4, 6, 8, 9] + .into_iter() + .enumerate() + .map(|(i, x)| (i + 100, x)) + .collect(); + assert_eq!(b.binary_search_by_key(&5, |_, &x| x), Err(3)); + assert_eq!(b.binary_search_by_key(&6, |_, &x| x), Ok(3)); + assert_eq!(b.binary_search_by_key(&7, |_, &x| x), Err(4)); + assert_eq!(b.binary_search_by_key(&8, |_, &x| x), Ok(4)); + + let b: IndexMap<_, i32> = [1, 2, 4, 5, 6, 8] + .into_iter() + .enumerate() + .map(|(i, x)| (i + 100, x)) + .collect(); + assert_eq!(b.binary_search_by_key(&9, |_, &x| x), Err(6)); + + let b: IndexMap<_, i32> = [1, 2, 4, 6, 7, 8, 9] + .into_iter() + .enumerate() + .map(|(i, x)| (i + 100, x)) + .collect(); + assert_eq!(b.binary_search_by_key(&6, |_, &x| x), Ok(3)); + assert_eq!(b.binary_search_by_key(&5, |_, &x| x), Err(3)); + assert_eq!(b.binary_search_by_key(&8, |_, &x| x), Ok(5)); + + let b: IndexMap<_, i32> = [1, 2, 4, 5, 6, 8, 9] + .into_iter() + .enumerate() + .map(|(i, x)| (i + 100, x)) + .collect(); + assert_eq!(b.binary_search_by_key(&7, |_, &x| x), Err(5)); + assert_eq!(b.binary_search_by_key(&0, |_, &x| x), Err(0)); + + let b: IndexMap<_, i32> = [1, 3, 3, 3, 7] + .into_iter() + .enumerate() + .map(|(i, x)| (i + 100, x)) + .collect(); + assert_eq!(b.binary_search_by_key(&0, |_, &x| x), Err(0)); + assert_eq!(b.binary_search_by_key(&1, |_, &x| x), Ok(0)); + assert_eq!(b.binary_search_by_key(&2, |_, &x| x), Err(1)); + assert!(match b.binary_search_by_key(&3, |_, &x| x) { + Ok(1..=3) => true, + _ => false, + }); + assert!(match b.binary_search_by_key(&3, |_, &x| x) { + Ok(1..=3) => true, + _ => false, + }); + assert_eq!(b.binary_search_by_key(&4, |_, &x| x), Err(4)); + assert_eq!(b.binary_search_by_key(&5, |_, &x| x), Err(4)); + assert_eq!(b.binary_search_by_key(&6, |_, &x| x), Err(4)); + assert_eq!(b.binary_search_by_key(&7, |_, &x| x), Ok(4)); + assert_eq!(b.binary_search_by_key(&8, |_, &x| x), Err(5)); +} + +#[test] +fn test_partition_point() { + // addaped from stds test for partition_point + let b: IndexMap<_, i32> = [] + .into_iter() + .enumerate() + .map(|(i, x)| (i + 100, x)) + .collect(); + assert_eq!(b.partition_point(|_, &x| x < 5), 0); + + let b: IndexMap<_, i32> = [4] + .into_iter() + .enumerate() + .map(|(i, x)| (i + 100, x)) + .collect(); + assert_eq!(b.partition_point(|_, &x| x < 3), 0); + assert_eq!(b.partition_point(|_, &x| x < 4), 0); + assert_eq!(b.partition_point(|_, &x| x < 5), 1); + + let b: IndexMap<_, i32> = [1, 2, 4, 6, 8, 9] + .into_iter() + .enumerate() + .map(|(i, x)| (i + 100, x)) + .collect(); + assert_eq!(b.partition_point(|_, &x| x < 5), 3); + assert_eq!(b.partition_point(|_, &x| x < 6), 3); + assert_eq!(b.partition_point(|_, &x| x < 7), 4); + assert_eq!(b.partition_point(|_, &x| x < 8), 4); + + let b: IndexMap<_, i32> = [1, 2, 4, 5, 6, 8] + .into_iter() + .enumerate() + .map(|(i, x)| (i + 100, x)) + .collect(); + assert_eq!(b.partition_point(|_, &x| x < 9), 6); + + let b: IndexMap<_, i32> = [1, 2, 4, 6, 7, 8, 9] + .into_iter() + .enumerate() + .map(|(i, x)| (i + 100, x)) + .collect(); + assert_eq!(b.partition_point(|_, &x| x < 6), 3); + assert_eq!(b.partition_point(|_, &x| x < 5), 3); + assert_eq!(b.partition_point(|_, &x| x < 8), 5); + + let b: IndexMap<_, i32> = [1, 2, 4, 5, 6, 8, 9] + .into_iter() + .enumerate() + .map(|(i, x)| (i + 100, x)) + .collect(); + assert_eq!(b.partition_point(|_, &x| x < 7), 5); + assert_eq!(b.partition_point(|_, &x| x < 0), 0); + + let b: IndexMap<_, i32> = [1, 3, 3, 3, 7] + .into_iter() + .enumerate() + .map(|(i, x)| (i + 100, x)) + .collect(); + assert_eq!(b.partition_point(|_, &x| x < 0), 0); + assert_eq!(b.partition_point(|_, &x| x < 1), 0); + assert_eq!(b.partition_point(|_, &x| x < 2), 1); + assert_eq!(b.partition_point(|_, &x| x < 3), 1); + assert_eq!(b.partition_point(|_, &x| x < 4), 4); + assert_eq!(b.partition_point(|_, &x| x < 5), 4); + assert_eq!(b.partition_point(|_, &x| x < 6), 4); + assert_eq!(b.partition_point(|_, &x| x < 7), 4); + assert_eq!(b.partition_point(|_, &x| x < 8), 5); +} diff --git a/src/set/tests.rs b/src/set/tests.rs index 44f8ed84..19509ee3 100644 --- a/src/set/tests.rs +++ b/src/set/tests.rs @@ -543,3 +543,142 @@ fn iter_default() { assert_default::>(); assert_default::>(); } + +#[test] +fn test_binary_search_by() { + // addaped from stds test for binary_search + let b: IndexSet = [].into(); + assert_eq!(b.binary_search_by(|x| x.cmp(&5)), Err(0)); + + let b: IndexSet = [4].into(); + assert_eq!(b.binary_search_by(|x| x.cmp(&3)), Err(0)); + assert_eq!(b.binary_search_by(|x| x.cmp(&4)), Ok(0)); + assert_eq!(b.binary_search_by(|x| x.cmp(&5)), Err(1)); + + let b: IndexSet = [1, 2, 4, 6, 8, 9].into(); + assert_eq!(b.binary_search_by(|x| x.cmp(&5)), Err(3)); + assert_eq!(b.binary_search_by(|x| x.cmp(&6)), Ok(3)); + assert_eq!(b.binary_search_by(|x| x.cmp(&7)), Err(4)); + assert_eq!(b.binary_search_by(|x| x.cmp(&8)), Ok(4)); + + let b: IndexSet = [1, 2, 4, 5, 6, 8].into(); + assert_eq!(b.binary_search_by(|x| x.cmp(&9)), Err(6)); + + let b: IndexSet = [1, 2, 4, 6, 7, 8, 9].into(); + assert_eq!(b.binary_search_by(|x| x.cmp(&6)), Ok(3)); + assert_eq!(b.binary_search_by(|x| x.cmp(&5)), Err(3)); + assert_eq!(b.binary_search_by(|x| x.cmp(&8)), Ok(5)); + + let b: IndexSet = [1, 2, 4, 5, 6, 8, 9].into(); + assert_eq!(b.binary_search_by(|x| x.cmp(&7)), Err(5)); + assert_eq!(b.binary_search_by(|x| x.cmp(&0)), Err(0)); + + let b: IndexSet = [1, 3, 3, 3, 7].into(); + assert_eq!(b.binary_search_by(|x| x.cmp(&0)), Err(0)); + assert_eq!(b.binary_search_by(|x| x.cmp(&1)), Ok(0)); + assert_eq!(b.binary_search_by(|x| x.cmp(&2)), Err(1)); + // diff from std as set the duplicates keys + assert!(match b.binary_search_by(|x| x.cmp(&3)) { + Ok(1..=2) => true, + _ => false, + }); + assert!(match b.binary_search_by(|x| x.cmp(&3)) { + Ok(1..=2) => true, + _ => false, + }); + assert_eq!(b.binary_search_by(|x| x.cmp(&4)), Err(2)); + assert_eq!(b.binary_search_by(|x| x.cmp(&5)), Err(2)); + assert_eq!(b.binary_search_by(|x| x.cmp(&6)), Err(2)); + assert_eq!(b.binary_search_by(|x| x.cmp(&7)), Ok(2)); + assert_eq!(b.binary_search_by(|x| x.cmp(&8)), Err(3)); +} + +#[test] +fn test_binary_search_by_key() { + // addaped from stds test for binary_search + let b: IndexSet = [].into(); + assert_eq!(b.binary_search_by_key(&5, |&x| x), Err(0)); + + let b: IndexSet = [4].into(); + assert_eq!(b.binary_search_by_key(&3, |&x| x), Err(0)); + assert_eq!(b.binary_search_by_key(&4, |&x| x), Ok(0)); + assert_eq!(b.binary_search_by_key(&5, |&x| x), Err(1)); + + let b: IndexSet = [1, 2, 4, 6, 8, 9].into(); + assert_eq!(b.binary_search_by_key(&5, |&x| x), Err(3)); + assert_eq!(b.binary_search_by_key(&6, |&x| x), Ok(3)); + assert_eq!(b.binary_search_by_key(&7, |&x| x), Err(4)); + assert_eq!(b.binary_search_by_key(&8, |&x| x), Ok(4)); + + let b: IndexSet = [1, 2, 4, 5, 6, 8].into(); + assert_eq!(b.binary_search_by_key(&9, |&x| x), Err(6)); + + let b: IndexSet = [1, 2, 4, 6, 7, 8, 9].into(); + assert_eq!(b.binary_search_by_key(&6, |&x| x), Ok(3)); + assert_eq!(b.binary_search_by_key(&5, |&x| x), Err(3)); + assert_eq!(b.binary_search_by_key(&8, |&x| x), Ok(5)); + + let b: IndexSet = [1, 2, 4, 5, 6, 8, 9].into(); + assert_eq!(b.binary_search_by_key(&7, |&x| x), Err(5)); + assert_eq!(b.binary_search_by_key(&0, |&x| x), Err(0)); + + let b: IndexSet = [1, 3, 3, 3, 7].into(); + assert_eq!(b.binary_search_by_key(&0, |&x| x), Err(0)); + assert_eq!(b.binary_search_by_key(&1, |&x| x), Ok(0)); + assert_eq!(b.binary_search_by_key(&2, |&x| x), Err(1)); + // diff from std as set the duplicates keys + assert!(match b.binary_search_by_key(&3, |&x| x) { + Ok(1..=2) => true, + _ => false, + }); + assert!(match b.binary_search_by_key(&3, |&x| x) { + Ok(1..=2) => true, + _ => false, + }); + assert_eq!(b.binary_search_by_key(&4, |&x| x), Err(2)); + assert_eq!(b.binary_search_by_key(&5, |&x| x), Err(2)); + assert_eq!(b.binary_search_by_key(&6, |&x| x), Err(2)); + assert_eq!(b.binary_search_by_key(&7, |&x| x), Ok(2)); + assert_eq!(b.binary_search_by_key(&8, |&x| x), Err(3)); +} + +#[test] +fn test_partition_point() { + // addaped from stds test for partition_point + let b: IndexSet = [].into(); + assert_eq!(b.partition_point(|&x| x < 5), 0); + + let b: IndexSet<_> = [4].into(); + assert_eq!(b.partition_point(|&x| x < 3), 0); + assert_eq!(b.partition_point(|&x| x < 4), 0); + assert_eq!(b.partition_point(|&x| x < 5), 1); + + let b: IndexSet<_> = [1, 2, 4, 6, 8, 9].into(); + assert_eq!(b.partition_point(|&x| x < 5), 3); + assert_eq!(b.partition_point(|&x| x < 6), 3); + assert_eq!(b.partition_point(|&x| x < 7), 4); + assert_eq!(b.partition_point(|&x| x < 8), 4); + + let b: IndexSet<_> = [1, 2, 4, 5, 6, 8].into(); + assert_eq!(b.partition_point(|&x| x < 9), 6); + + let b: IndexSet<_> = [1, 2, 4, 6, 7, 8, 9].into(); + assert_eq!(b.partition_point(|&x| x < 6), 3); + assert_eq!(b.partition_point(|&x| x < 5), 3); + assert_eq!(b.partition_point(|&x| x < 8), 5); + + let b: IndexSet<_> = [1, 2, 4, 5, 6, 8, 9].into(); + assert_eq!(b.partition_point(|&x| x < 7), 5); + assert_eq!(b.partition_point(|&x| x < 0), 0); + + let b: IndexSet<_> = [1, 3, 3, 3, 7].into(); + assert_eq!(b.partition_point(|&x| x < 0), 0); + assert_eq!(b.partition_point(|&x| x < 1), 0); + assert_eq!(b.partition_point(|&x| x < 2), 1); + assert_eq!(b.partition_point(|&x| x < 3), 1); + assert_eq!(b.partition_point(|&x| x < 4), 2); // diff from std as set the duplicates keys + assert_eq!(b.partition_point(|&x| x < 5), 2); + assert_eq!(b.partition_point(|&x| x < 6), 2); + assert_eq!(b.partition_point(|&x| x < 7), 2); + assert_eq!(b.partition_point(|&x| x < 8), 3); +} From 3263c0f03fca5fa42902118adbdfb4854db17633 Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Thu, 26 Oct 2023 19:50:29 +0000 Subject: [PATCH 135/236] fix wording --- src/map.rs | 23 +++++++++++++---------- src/map/slice.rs | 33 +++++++++++++++++++-------------- src/map/tests.rs | 6 +++--- src/set.rs | 23 +++++++++++++---------- src/set/slice.rs | 31 ++++++++++++++++++------------- src/set/tests.rs | 12 ++++++------ 6 files changed, 72 insertions(+), 56 deletions(-) diff --git a/src/map.rs b/src/map.rs index af724940..4f130aea 100644 --- a/src/map.rs +++ b/src/map.rs @@ -796,9 +796,10 @@ where /// Search over a sorted map with a comparator function. /// - /// Returns the position where that value is present, or the position where can be inserted to maintain the sort. - /// see [slice::binary_search_by] for more details. - /// **O(log(n))** + /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. + /// see [`slice::binary_search_by`] for more details. + /// + /// Computes in **O(log(n))** time. #[inline] pub fn binary_search_by<'a, F>(&'a self, f: F) -> Result where @@ -807,11 +808,12 @@ where self.as_slice().binary_search_by(f) } - /// Search over a sorted map with a key extraction function. + /// Search over a sorted map with an extraction function. + /// + /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. + /// see [`slice::binary_search_by_key`] for more details. /// - /// Returns the position where that value is present, or the position where can be inserted to maintain the sort. - /// see [slice::binary_search_by_key] for more details. - /// **O(log(n))** + /// Computes in **O(log(n))** time. #[inline] pub fn binary_search_by_key<'a, B, F>(&'a self, b: &B, f: F) -> Result where @@ -821,11 +823,12 @@ where self.as_slice().binary_search_by_key(b, f) } - /// Returns the index of the partition point or a sorted map according to the given predicate + /// Returns the index of the partition point of a sorted map according to the given predicate /// (the index of the first element of the second partition). /// - /// see [slice::partition_point] for more details. - /// **O(log(n))** + /// see [`slice::partition_point`] for more details. + /// + /// Computes in **O(log(n))** time. #[must_use] pub fn partition_point

(&self, pred: P) -> usize where diff --git a/src/map/slice.rs b/src/map/slice.rs index ec3c914b..0ee46389 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -202,11 +202,13 @@ impl Slice { IntoValues::new(self.into_entries()) } - /// Search over a sorted map for a value. + /// Search over a sorted map for a key. /// - /// Returns the position where that value is present, or the position where can be inserted to maintain the sort. - /// see [slice::binary_search] for more details. - /// **O(log(n))**, which is notably less scalable than looking the value up in the map set is a slice from. + /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. + /// see [`slice::binary_search`] for more details. + /// + /// Computes in **O(log(n))** time, + /// which is notably less scalable than looking the value up in the map this is a slice from. pub fn binary_search_keys(&self, x: &K) -> Result where K: Ord, @@ -216,9 +218,10 @@ impl Slice { /// Search over a sorted map with a comparator function. /// - /// Returns the position where that value is present, or the position where can be inserted to maintain the sort. - /// see [slice::binary_search_by] for more details. - /// **O(log(n))** + /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. + /// see [`slice::binary_search_by`] for more details. + /// + /// Computes in **O(log(n))** time. #[inline] pub fn binary_search_by<'a, F>(&'a self, mut f: F) -> Result where @@ -227,11 +230,12 @@ impl Slice { self.entries.binary_search_by(move |a| f(&a.key, &a.value)) } - /// Search over a sorted map with a key extraction function. + /// Search over a sorted map with an extraction function. /// - /// Returns the position where that value is present, or the position where can be inserted to maintain the sort. - /// see [slice::binary_search_by_key] for more details. - /// **O(log(n))** + /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. + /// see [`slice::binary_search_by_key`] for more details. + /// + /// Computes in **O(log(n))** time. #[inline] pub fn binary_search_by_key<'a, B, F>(&'a self, b: &B, mut f: F) -> Result where @@ -241,11 +245,12 @@ impl Slice { self.binary_search_by(|k, v| f(k, v).cmp(b)) } - /// Returns the index of the partition point or a sorted map according to the given predicate + /// Returns the index of the partition point of a sorted map according to the given predicate /// (the index of the first element of the second partition). /// - /// see [slice::partition_point] for more details. - /// **O(log(n))** + /// see [`slice::partition_point`] for more details. + /// + /// Computes in **O(log(n))** time. #[must_use] pub fn partition_point

(&self, mut pred: P) -> usize where diff --git a/src/map/tests.rs b/src/map/tests.rs index 3f213212..3d2315d3 100644 --- a/src/map/tests.rs +++ b/src/map/tests.rs @@ -450,7 +450,7 @@ fn iter_default() { #[test] fn test_binary_search_by() { - // addaped from stds test for binary_search + // adapted from std's test for binary_search let b: IndexMap<_, i32> = [] .into_iter() .enumerate() @@ -526,7 +526,7 @@ fn test_binary_search_by() { #[test] fn test_binary_search_by_key() { - // addaped from stds test for binary_search + // adapted from std's test for binary_search let b: IndexMap<_, i32> = [] .into_iter() .enumerate() @@ -602,7 +602,7 @@ fn test_binary_search_by_key() { #[test] fn test_partition_point() { - // addaped from stds test for partition_point + // adapted from std's test for partition_point let b: IndexMap<_, i32> = [] .into_iter() .enumerate() diff --git a/src/set.rs b/src/set.rs index bfb0e528..08dfb043 100644 --- a/src/set.rs +++ b/src/set.rs @@ -690,9 +690,10 @@ where /// Search over a sorted set with a comparator function. /// - /// Returns the position where that value is present, or the position where can be inserted to maintain the sort. - /// see [slice::binary_search_by] for more details. - /// **O(log(n))** + /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. + /// see [`slice::binary_search_by`] for more details. + /// + /// Computes in **O(log(n))** time. #[inline] pub fn binary_search_by<'a, F>(&'a self, f: F) -> Result where @@ -701,11 +702,12 @@ where self.as_slice().binary_search_by(f) } - /// Search over a sorted set with a key extraction function. + /// Search over a sorted set with an extraction function. + /// + /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. + /// see [`slice::binary_search_by_key`] for more details. /// - /// Returns the position where that value is present, or the position where can be inserted to maintain the sort. - /// see [slice::binary_search_by_key] for more details. - /// **O(log(n))** + /// Computes in **O(log(n))** time. #[inline] pub fn binary_search_by_key<'a, B, F>(&'a self, b: &B, f: F) -> Result where @@ -715,11 +717,12 @@ where self.as_slice().binary_search_by_key(b, f) } - /// Returns the index of the partition point or a sorted set according to the given predicate + /// Returns the index of the partition point of a sorted set according to the given predicate /// (the index of the first element of the second partition). /// - /// see [slice::partition_point] for more details. - /// **O(log(n))** + /// see [`slice::partition_point`] for more details. + /// + /// Computes in **O(log(n))** time. #[must_use] pub fn partition_point

(&self, pred: P) -> usize where diff --git a/src/set/slice.rs b/src/set/slice.rs index 9f2a2d53..15a320c1 100644 --- a/src/set/slice.rs +++ b/src/set/slice.rs @@ -112,9 +112,11 @@ impl Slice { /// Search over a sorted set for a value. /// - /// Returns the position where that value is present, or the position where can be inserted to maintain the sort. - /// see [slice::binary_search] for more details. - /// **O(log(n))**, which is notably less scalable than looking the value up in the set this is a slice from. + /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. + /// see [`slice::binary_search`] for more details. + /// + /// Computes in **O(log(n))** time, + /// which is notably less scalable than looking the value up in the set this is a slice from. pub fn binary_search(&self, x: &T) -> Result where T: Ord, @@ -124,9 +126,10 @@ impl Slice { /// Search over a sorted set with a comparator function. /// - /// Returns the position where that value is present, or the position where can be inserted to maintain the sort. - /// see [slice::binary_search_by] for more details. - /// **O(log(n))** + /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. + /// see [`slice::binary_search_by`] for more details. + /// + /// Computes in **O(log(n))** time. #[inline] pub fn binary_search_by<'a, F>(&'a self, mut f: F) -> Result where @@ -135,11 +138,12 @@ impl Slice { self.entries.binary_search_by(move |a| f(&a.key)) } - /// Search over a sorted set with a key extraction function. + /// Search over a sorted set with an extraction function. /// - /// Returns the position where that value is present, or the position where can be inserted to maintain the sort. - /// see [slice::binary_search_by_key] for more details. - /// **O(log(n))** + /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. + /// see [`slice::binary_search_by_key`] for more details. + /// + /// Computes in **O(log(n))** time. #[inline] pub fn binary_search_by_key<'a, B, F>(&'a self, b: &B, mut f: F) -> Result where @@ -149,11 +153,12 @@ impl Slice { self.binary_search_by(|k| f(k).cmp(b)) } - /// Returns the index of the partition point or a sorted set according to the given predicate + /// Returns the index of the partition point of a sorted set according to the given predicate /// (the index of the first element of the second partition). /// - /// see [slice::partition_point] for more details. - /// **O(log(n))** + /// see [`slice::partition_point`] for more details. + /// + /// Computes in **O(log(n))** time. #[must_use] pub fn partition_point

(&self, mut pred: P) -> usize where diff --git a/src/set/tests.rs b/src/set/tests.rs index 19509ee3..d7bb9de6 100644 --- a/src/set/tests.rs +++ b/src/set/tests.rs @@ -546,7 +546,7 @@ fn iter_default() { #[test] fn test_binary_search_by() { - // addaped from stds test for binary_search + // adapted from std's test for binary_search let b: IndexSet = [].into(); assert_eq!(b.binary_search_by(|x| x.cmp(&5)), Err(0)); @@ -577,7 +577,7 @@ fn test_binary_search_by() { assert_eq!(b.binary_search_by(|x| x.cmp(&0)), Err(0)); assert_eq!(b.binary_search_by(|x| x.cmp(&1)), Ok(0)); assert_eq!(b.binary_search_by(|x| x.cmp(&2)), Err(1)); - // diff from std as set the duplicates keys + // diff from std as set merges the duplicate keys assert!(match b.binary_search_by(|x| x.cmp(&3)) { Ok(1..=2) => true, _ => false, @@ -595,7 +595,7 @@ fn test_binary_search_by() { #[test] fn test_binary_search_by_key() { - // addaped from stds test for binary_search + // adapted from std's test for binary_search let b: IndexSet = [].into(); assert_eq!(b.binary_search_by_key(&5, |&x| x), Err(0)); @@ -626,7 +626,7 @@ fn test_binary_search_by_key() { assert_eq!(b.binary_search_by_key(&0, |&x| x), Err(0)); assert_eq!(b.binary_search_by_key(&1, |&x| x), Ok(0)); assert_eq!(b.binary_search_by_key(&2, |&x| x), Err(1)); - // diff from std as set the duplicates keys + // diff from std as set merges the duplicate keys assert!(match b.binary_search_by_key(&3, |&x| x) { Ok(1..=2) => true, _ => false, @@ -644,7 +644,7 @@ fn test_binary_search_by_key() { #[test] fn test_partition_point() { - // addaped from stds test for partition_point + // adapted from std's test for partition_point let b: IndexSet = [].into(); assert_eq!(b.partition_point(|&x| x < 5), 0); @@ -676,7 +676,7 @@ fn test_partition_point() { assert_eq!(b.partition_point(|&x| x < 1), 0); assert_eq!(b.partition_point(|&x| x < 2), 1); assert_eq!(b.partition_point(|&x| x < 3), 1); - assert_eq!(b.partition_point(|&x| x < 4), 2); // diff from std as set the duplicates keys + assert_eq!(b.partition_point(|&x| x < 4), 2); // diff from std as set merges the duplicate keys assert_eq!(b.partition_point(|&x| x < 5), 2); assert_eq!(b.partition_point(|&x| x < 6), 2); assert_eq!(b.partition_point(|&x| x < 7), 2); From 1350db6fd48ca4ad6d9843681c6f7d710885f6fb Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 31 Oct 2023 12:17:45 -0700 Subject: [PATCH 136/236] Capitalize "see" in binary search docs --- src/map.rs | 6 +++--- src/map/slice.rs | 8 ++++---- src/set.rs | 6 +++--- src/set/slice.rs | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/map.rs b/src/map.rs index 4f130aea..2a204ebf 100644 --- a/src/map.rs +++ b/src/map.rs @@ -797,7 +797,7 @@ where /// Search over a sorted map with a comparator function. /// /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. - /// see [`slice::binary_search_by`] for more details. + /// See [`slice::binary_search_by`] for more details. /// /// Computes in **O(log(n))** time. #[inline] @@ -811,7 +811,7 @@ where /// Search over a sorted map with an extraction function. /// /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. - /// see [`slice::binary_search_by_key`] for more details. + /// See [`slice::binary_search_by_key`] for more details. /// /// Computes in **O(log(n))** time. #[inline] @@ -826,7 +826,7 @@ where /// Returns the index of the partition point of a sorted map according to the given predicate /// (the index of the first element of the second partition). /// - /// see [`slice::partition_point`] for more details. + /// See [`slice::partition_point`] for more details. /// /// Computes in **O(log(n))** time. #[must_use] diff --git a/src/map/slice.rs b/src/map/slice.rs index 0ee46389..3b999bf0 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -205,7 +205,7 @@ impl Slice { /// Search over a sorted map for a key. /// /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. - /// see [`slice::binary_search`] for more details. + /// See [`slice::binary_search`] for more details. /// /// Computes in **O(log(n))** time, /// which is notably less scalable than looking the value up in the map this is a slice from. @@ -219,7 +219,7 @@ impl Slice { /// Search over a sorted map with a comparator function. /// /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. - /// see [`slice::binary_search_by`] for more details. + /// See [`slice::binary_search_by`] for more details. /// /// Computes in **O(log(n))** time. #[inline] @@ -233,7 +233,7 @@ impl Slice { /// Search over a sorted map with an extraction function. /// /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. - /// see [`slice::binary_search_by_key`] for more details. + /// See [`slice::binary_search_by_key`] for more details. /// /// Computes in **O(log(n))** time. #[inline] @@ -248,7 +248,7 @@ impl Slice { /// Returns the index of the partition point of a sorted map according to the given predicate /// (the index of the first element of the second partition). /// - /// see [`slice::partition_point`] for more details. + /// See [`slice::partition_point`] for more details. /// /// Computes in **O(log(n))** time. #[must_use] diff --git a/src/set.rs b/src/set.rs index 08dfb043..bbeef6e1 100644 --- a/src/set.rs +++ b/src/set.rs @@ -691,7 +691,7 @@ where /// Search over a sorted set with a comparator function. /// /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. - /// see [`slice::binary_search_by`] for more details. + /// See [`slice::binary_search_by`] for more details. /// /// Computes in **O(log(n))** time. #[inline] @@ -705,7 +705,7 @@ where /// Search over a sorted set with an extraction function. /// /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. - /// see [`slice::binary_search_by_key`] for more details. + /// See [`slice::binary_search_by_key`] for more details. /// /// Computes in **O(log(n))** time. #[inline] @@ -720,7 +720,7 @@ where /// Returns the index of the partition point of a sorted set according to the given predicate /// (the index of the first element of the second partition). /// - /// see [`slice::partition_point`] for more details. + /// See [`slice::partition_point`] for more details. /// /// Computes in **O(log(n))** time. #[must_use] diff --git a/src/set/slice.rs b/src/set/slice.rs index 15a320c1..bc39e4af 100644 --- a/src/set/slice.rs +++ b/src/set/slice.rs @@ -113,7 +113,7 @@ impl Slice { /// Search over a sorted set for a value. /// /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. - /// see [`slice::binary_search`] for more details. + /// See [`slice::binary_search`] for more details. /// /// Computes in **O(log(n))** time, /// which is notably less scalable than looking the value up in the set this is a slice from. @@ -127,7 +127,7 @@ impl Slice { /// Search over a sorted set with a comparator function. /// /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. - /// see [`slice::binary_search_by`] for more details. + /// See [`slice::binary_search_by`] for more details. /// /// Computes in **O(log(n))** time. #[inline] @@ -141,7 +141,7 @@ impl Slice { /// Search over a sorted set with an extraction function. /// /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. - /// see [`slice::binary_search_by_key`] for more details. + /// See [`slice::binary_search_by_key`] for more details. /// /// Computes in **O(log(n))** time. #[inline] @@ -156,7 +156,7 @@ impl Slice { /// Returns the index of the partition point of a sorted set according to the given predicate /// (the index of the first element of the second partition). /// - /// see [`slice::partition_point`] for more details. + /// See [`slice::partition_point`] for more details. /// /// Computes in **O(log(n))** time. #[must_use] From eec4c437ea435bfae4e507fe713cde225a63598f Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 31 Oct 2023 12:44:25 -0700 Subject: [PATCH 137/236] Add plain binary search to maps and sets too While `IndexMap::binary_search_keys` and `IndexSet::binary_search` don't scale as well as their corresponding `get_index_of`, O(log n) vs. O(1), they do still offer the benefit of returning an `Err` insertion point. --- src/map.rs | 15 +++++++++++++++ src/map/slice.rs | 5 +++-- src/set.rs | 15 +++++++++++++++ src/set/slice.rs | 3 ++- 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/map.rs b/src/map.rs index 2a204ebf..bd7cd241 100644 --- a/src/map.rs +++ b/src/map.rs @@ -794,6 +794,21 @@ where }); } + /// Search over a sorted map for a key. + /// + /// Returns the position where that key is present, or the position where it can be inserted to maintain the sort. + /// See [`slice::binary_search`] for more details. + /// + /// Computes in **O(log(n))** time, + /// which is notably less scalable than looking the key up using [`get_index_of`], + /// but this can also position missing keys. + pub fn binary_search_keys(&self, x: &K) -> Result + where + K: Ord, + { + self.as_slice().binary_search_keys(x) + } + /// Search over a sorted map with a comparator function. /// /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. diff --git a/src/map/slice.rs b/src/map/slice.rs index 3b999bf0..89674469 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -204,11 +204,12 @@ impl Slice { /// Search over a sorted map for a key. /// - /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. + /// Returns the position where that key is present, or the position where it can be inserted to maintain the sort. /// See [`slice::binary_search`] for more details. /// /// Computes in **O(log(n))** time, - /// which is notably less scalable than looking the value up in the map this is a slice from. + /// which is notably less scalable than looking the key up in the map this is a slice from + /// using [`IndexMap::get_index_of`], but this can also position missing keys. pub fn binary_search_keys(&self, x: &K) -> Result where K: Ord, diff --git a/src/set.rs b/src/set.rs index bbeef6e1..a7de3e29 100644 --- a/src/set.rs +++ b/src/set.rs @@ -688,6 +688,21 @@ where }); } + /// Search over a sorted set for a value. + /// + /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. + /// See [`slice::binary_search`] for more details. + /// + /// Computes in **O(log(n))** time, + /// which is notably less scalable than looking the value up using [`get_index_of`], + /// but this can also position missing values. + pub fn binary_search(&self, x: &T) -> Result + where + T: Ord, + { + self.as_slice().binary_search(x) + } + /// Search over a sorted set with a comparator function. /// /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. diff --git a/src/set/slice.rs b/src/set/slice.rs index bc39e4af..8da4578c 100644 --- a/src/set/slice.rs +++ b/src/set/slice.rs @@ -116,7 +116,8 @@ impl Slice { /// See [`slice::binary_search`] for more details. /// /// Computes in **O(log(n))** time, - /// which is notably less scalable than looking the value up in the set this is a slice from. + /// which is notably less scalable than looking the value up in the set this is a slice from + /// using [`IndexSet::get_index_of`], but this can also position missing values. pub fn binary_search(&self, x: &T) -> Result where T: Ord, From 7326a6e1ac696b479c3d2595c91a8393c1bf32e2 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 31 Oct 2023 12:59:49 -0700 Subject: [PATCH 138/236] Re-wrap the binary search docs --- src/map.rs | 17 ++++++++--------- src/map/slice.rs | 18 +++++++++--------- src/set.rs | 17 ++++++++--------- src/set/slice.rs | 18 +++++++++--------- 4 files changed, 34 insertions(+), 36 deletions(-) diff --git a/src/map.rs b/src/map.rs index bd7cd241..4ee24d5f 100644 --- a/src/map.rs +++ b/src/map.rs @@ -796,12 +796,11 @@ where /// Search over a sorted map for a key. /// - /// Returns the position where that key is present, or the position where it can be inserted to maintain the sort. - /// See [`slice::binary_search`] for more details. + /// Returns the position where that key is present, or the position where it can be inserted to + /// maintain the sort. See [`slice::binary_search`] for more details. /// - /// Computes in **O(log(n))** time, - /// which is notably less scalable than looking the key up using [`get_index_of`], - /// but this can also position missing keys. + /// Computes in **O(log(n))** time, which is notably less scalable than looking the key up + /// using [`get_index_of`][IndexMap::get_index_of], but this can also position missing keys. pub fn binary_search_keys(&self, x: &K) -> Result where K: Ord, @@ -811,8 +810,8 @@ where /// Search over a sorted map with a comparator function. /// - /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. - /// See [`slice::binary_search_by`] for more details. + /// Returns the position where that value is present, or the position where it can be inserted + /// to maintain the sort. See [`slice::binary_search_by`] for more details. /// /// Computes in **O(log(n))** time. #[inline] @@ -825,8 +824,8 @@ where /// Search over a sorted map with an extraction function. /// - /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. - /// See [`slice::binary_search_by_key`] for more details. + /// Returns the position where that value is present, or the position where it can be inserted + /// to maintain the sort. See [`slice::binary_search_by_key`] for more details. /// /// Computes in **O(log(n))** time. #[inline] diff --git a/src/map/slice.rs b/src/map/slice.rs index 89674469..e2e30961 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -204,12 +204,12 @@ impl Slice { /// Search over a sorted map for a key. /// - /// Returns the position where that key is present, or the position where it can be inserted to maintain the sort. - /// See [`slice::binary_search`] for more details. + /// Returns the position where that key is present, or the position where it can be inserted to + /// maintain the sort. See [`slice::binary_search`] for more details. /// - /// Computes in **O(log(n))** time, - /// which is notably less scalable than looking the key up in the map this is a slice from - /// using [`IndexMap::get_index_of`], but this can also position missing keys. + /// Computes in **O(log(n))** time, which is notably less scalable than looking the key up in + /// the map this is a slice from using [`IndexMap::get_index_of`], but this can also position + /// missing keys. pub fn binary_search_keys(&self, x: &K) -> Result where K: Ord, @@ -219,8 +219,8 @@ impl Slice { /// Search over a sorted map with a comparator function. /// - /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. - /// See [`slice::binary_search_by`] for more details. + /// Returns the position where that value is present, or the position where it can be inserted + /// to maintain the sort. See [`slice::binary_search_by`] for more details. /// /// Computes in **O(log(n))** time. #[inline] @@ -233,8 +233,8 @@ impl Slice { /// Search over a sorted map with an extraction function. /// - /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. - /// See [`slice::binary_search_by_key`] for more details. + /// Returns the position where that value is present, or the position where it can be inserted + /// to maintain the sort. See [`slice::binary_search_by_key`] for more details. /// /// Computes in **O(log(n))** time. #[inline] diff --git a/src/set.rs b/src/set.rs index a7de3e29..c047b0be 100644 --- a/src/set.rs +++ b/src/set.rs @@ -690,12 +690,11 @@ where /// Search over a sorted set for a value. /// - /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. - /// See [`slice::binary_search`] for more details. + /// Returns the position where that value is present, or the position where it can be inserted + /// to maintain the sort. See [`slice::binary_search`] for more details. /// - /// Computes in **O(log(n))** time, - /// which is notably less scalable than looking the value up using [`get_index_of`], - /// but this can also position missing values. + /// Computes in **O(log(n))** time, which is notably less scalable than looking the value up + /// using [`get_index_of`][IndexSet::get_index_of], but this can also position missing values. pub fn binary_search(&self, x: &T) -> Result where T: Ord, @@ -705,8 +704,8 @@ where /// Search over a sorted set with a comparator function. /// - /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. - /// See [`slice::binary_search_by`] for more details. + /// Returns the position where that value is present, or the position where it can be inserted + /// to maintain the sort. See [`slice::binary_search_by`] for more details. /// /// Computes in **O(log(n))** time. #[inline] @@ -719,8 +718,8 @@ where /// Search over a sorted set with an extraction function. /// - /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. - /// See [`slice::binary_search_by_key`] for more details. + /// Returns the position where that value is present, or the position where it can be inserted + /// to maintain the sort. See [`slice::binary_search_by_key`] for more details. /// /// Computes in **O(log(n))** time. #[inline] diff --git a/src/set/slice.rs b/src/set/slice.rs index 8da4578c..305157a6 100644 --- a/src/set/slice.rs +++ b/src/set/slice.rs @@ -112,12 +112,12 @@ impl Slice { /// Search over a sorted set for a value. /// - /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. - /// See [`slice::binary_search`] for more details. + /// Returns the position where that value is present, or the position where it can be inserted + /// to maintain the sort. See [`slice::binary_search`] for more details. /// - /// Computes in **O(log(n))** time, - /// which is notably less scalable than looking the value up in the set this is a slice from - /// using [`IndexSet::get_index_of`], but this can also position missing values. + /// Computes in **O(log(n))** time, which is notably less scalable than looking the value up in + /// the set this is a slice from using [`IndexSet::get_index_of`], but this can also position + /// missing values. pub fn binary_search(&self, x: &T) -> Result where T: Ord, @@ -127,8 +127,8 @@ impl Slice { /// Search over a sorted set with a comparator function. /// - /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. - /// See [`slice::binary_search_by`] for more details. + /// Returns the position where that value is present, or the position where it can be inserted + /// to maintain the sort. See [`slice::binary_search_by`] for more details. /// /// Computes in **O(log(n))** time. #[inline] @@ -141,8 +141,8 @@ impl Slice { /// Search over a sorted set with an extraction function. /// - /// Returns the position where that value is present, or the position where it can be inserted to maintain the sort. - /// See [`slice::binary_search_by_key`] for more details. + /// Returns the position where that value is present, or the position where it can be inserted + /// to maintain the sort. See [`slice::binary_search_by_key`] for more details. /// /// Computes in **O(log(n))** time. #[inline] From f1ea00731d418e4dde4350bf93b8b058d74cb316 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 31 Oct 2023 14:00:49 -0700 Subject: [PATCH 139/236] Release 2.1.0 --- Cargo.toml | 2 +- RELEASES.md | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e57c7333..dd38adee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "indexmap" edition = "2021" -version = "2.0.2" +version = "2.1.0" documentation = "https://docs.rs/indexmap/" repository = "https://github.com/bluss/indexmap" license = "Apache-2.0 OR MIT" diff --git a/RELEASES.md b/RELEASES.md index 0964d198..817a2dc2 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,15 @@ +- 2.1.0 + + - Empty slices can now be created with `map::Slice::{new, new_mut}` and + `set::Slice::new`. In addition, `Slice::new`, `len`, and `is_empty` are + now `const` functions on both types. + + - `IndexMap`, `IndexSet`, and their respective `Slice`s all have binary + search methods for sorted data: map `binary_search_keys` and set + `binary_search` for plain comparision, `binary_search_by` for custom + comparators, `binary_search_by_key` for key extraction, and + `partition_point` for boolean conditions. + - 2.0.2 - The `hashbrown` dependency has been updated to version 0.14.1 to From 555f6ea40d3cc96eed5f0b790e24cea80bd9826b Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 1 Nov 2023 14:18:26 -0700 Subject: [PATCH 140/236] `impl Index for Keys` --- src/map.rs | 4 +++ src/map/iter.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/src/map.rs b/src/map.rs index 4ee24d5f..4966d8bd 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1079,6 +1079,10 @@ where /// Access `IndexMap` values at indexed positions. /// +/// See [`Index for Keys`][keys] to access a map's keys instead. +/// +/// [keys]: Keys#impl-Index-for-Keys<'a,+K,+V> +/// /// # Examples /// /// ``` diff --git a/src/map/iter.rs b/src/map/iter.rs index db6e140d..97d4473e 100644 --- a/src/map/iter.rs +++ b/src/map/iter.rs @@ -3,6 +3,7 @@ use super::{Bucket, Entries, IndexMap, Slice}; use alloc::vec::{self, Vec}; use core::fmt; use core::iter::FusedIterator; +use core::ops::Index; use core::slice; impl<'a, K, V, S> IntoIterator for &'a IndexMap { @@ -326,6 +327,73 @@ impl Default for Keys<'_, K, V> { } } +/// Access `IndexMap` keys at indexed positions. +/// +/// While [`Index for IndexMap`][values] accesses a map's values, +/// indexing through [`IndexMap::keys`] offers an alternative to access a map's +/// keys instead. +/// +/// [values]: IndexMap#impl-Index-for-IndexMap +/// +/// Since `Keys` is also an iterator, consuming items from the iterator will +/// offset the effective indexes. Similarly, if `Keys` is obtained from +/// [`Slice::keys`], indexes will be interpreted relative to the position of +/// that slice. +/// +/// # Examples +/// +/// ``` +/// use indexmap::IndexMap; +/// +/// let mut map = IndexMap::new(); +/// for word in "Lorem ipsum dolor sit amet".split_whitespace() { +/// map.insert(word.to_lowercase(), word.to_uppercase()); +/// } +/// +/// assert_eq!(map[0], "LOREM"); +/// assert_eq!(map.keys()[0], "lorem"); +/// assert_eq!(map[1], "IPSUM"); +/// assert_eq!(map.keys()[1], "ipsum"); +/// +/// map.reverse(); +/// assert_eq!(map.keys()[0], "amet"); +/// assert_eq!(map.keys()[1], "sit"); +/// +/// map.sort_keys(); +/// assert_eq!(map.keys()[0], "amet"); +/// assert_eq!(map.keys()[1], "dolor"); +/// +/// // Advancing the iterator will offset the indexing +/// let mut keys = map.keys(); +/// assert_eq!(keys[0], "amet"); +/// assert_eq!(keys.next().map(|s| &**s), Some("amet")); +/// assert_eq!(keys[0], "dolor"); +/// assert_eq!(keys[1], "ipsum"); +/// +/// // Slices may have an offset as well +/// let slice = &map[2..]; +/// assert_eq!(slice[0], "IPSUM"); +/// assert_eq!(slice.keys()[0], "ipsum"); +/// ``` +/// +/// ```should_panic +/// use indexmap::IndexMap; +/// +/// let mut map = IndexMap::new(); +/// map.insert("foo", 1); +/// println!("{:?}", map.keys()[10]); // panics! +/// ``` +impl<'a, K, V> Index for Keys<'a, K, V> { + type Output = K; + + /// Returns a reference to the key at the supplied `index`. + /// + /// ***Panics*** if `index` is out of bounds. + fn index(&self, index: usize) -> &K { + &self.iter.as_slice()[index].key + } +} + /// An owning iterator over the keys of a `IndexMap`. /// /// This `struct` is created by the [`into_keys`] method on [`IndexMap`]. From af4e1a52e2eec6f856a3aad92611b22117c89b92 Mon Sep 17 00:00:00 2001 From: zachs18 <8355914+zachs18@users.noreply.github.com> Date: Fri, 29 Dec 2023 16:26:45 -0600 Subject: [PATCH 141/236] Remove doc `html_root_url` --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 5e427843..45d8b2e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,6 @@ // We *mostly* avoid unsafe code, but `map::core::raw` allows it to use `RawTable` buckets. #![deny(unsafe_code)] #![warn(rust_2018_idioms)] -#![doc(html_root_url = "https://docs.rs/indexmap/1/")] #![no_std] //! [`IndexMap`] is a hash table where the iteration order of the key-value From 10ced835899e5d48c76579ce7db7baba1a8f940f Mon Sep 17 00:00:00 2001 From: Bernhard Kragl Date: Mon, 1 Jan 2024 22:00:30 +0100 Subject: [PATCH 142/236] Add method to get a map entry by index --- src/map.rs | 14 ++++++- src/map/core.rs | 106 +++++++++++++++++++++++++++++++++++++++++++++++ src/map/tests.rs | 30 ++++++++++++++ 3 files changed, 149 insertions(+), 1 deletion(-) diff --git a/src/map.rs b/src/map.rs index 4ee24d5f..76eee91f 100644 --- a/src/map.rs +++ b/src/map.rs @@ -32,7 +32,7 @@ use alloc::vec::Vec; #[cfg(feature = "std")] use std::collections::hash_map::RandomState; -use self::core::IndexMapCore; +use self::core::{IndexMapCore, IndexedEntry}; use crate::util::{third, try_simplify_range}; use crate::{Bucket, Entries, Equivalent, HashValue, TryReserveError}; @@ -423,6 +423,18 @@ where self.core.entry(hash, key) } + /// Get an entry in the map by index for in-place manipulation. + /// + /// Valid indices are *0 <= index < self.len()* + /// + /// Computes in **O(1)** time. + pub fn get_index_entry(&mut self, index: usize) -> Option> { + if index >= self.len() { + return None; + } + Some(IndexedEntry::new(&mut self.core, index)) + } + /// Return `true` if an equivalent to `key` exists in the map. /// /// Computes in **O(1)** time (average). diff --git a/src/map/core.rs b/src/map/core.rs index 4a78035c..5e7f6e32 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -754,9 +754,115 @@ impl fmt::Debug for VacantEntry<'_, K, V> { } } +/// A view into an occupied entry in a `IndexMap` obtained by index. +/// +/// This struct is constructed from the `get_index_entry` method on `IndexMap`. +pub struct IndexedEntry<'a, K, V> { + map: &'a mut IndexMapCore, + // We have a mutable reference to the map, which keeps the index + // valid and pointing to the correct entry. + index: usize, +} + +impl<'a, K, V> IndexedEntry<'a, K, V> { + pub(crate) fn new(map: &'a mut IndexMapCore, index: usize) -> Self { + Self { map, index } + } + + /// Gets a reference to the entry's key in the map. + pub fn key(&self) -> &K { + &self.map.entries[self.index].key + } + + /// Gets a reference to the entry's value in the map. + pub fn get(&self) -> &V { + &self.map.entries[self.index].value + } + + /// Gets a mutable reference to the entry's value in the map. + /// + /// If you need a reference which may outlive the destruction of the + /// `IndexedEntry` value, see `into_mut`. + pub fn get_mut(&mut self) -> &mut V { + &mut self.map.entries[self.index].value + } + + /// Sets the value of the entry to `value`, and returns the entry's old value. + pub fn insert(&mut self, value: V) -> V { + mem::replace(self.get_mut(), value) + } + + /// Return the index of the key-value pair + #[inline] + pub fn index(&self) -> usize { + self.index + } + + /// Converts into a mutable reference to the entry's value in the map, + /// with a lifetime bound to the map itself. + pub fn into_mut(self) -> &'a mut V { + &mut self.map.entries[self.index].value + } + + /// Remove and return the key, value pair stored in the map for this entry + /// + /// Like `Vec::swap_remove`, the pair is removed by swapping it with the + /// last element of the map and popping it off. **This perturbs + /// the position of what used to be the last element!** + /// + /// Computes in **O(1)** time (average). + pub fn swap_remove_entry(self) -> (K, V) { + self.map.swap_remove_index(self.index).unwrap() + } + + /// Remove and return the key, value pair stored in the map for this entry + /// + /// Like `Vec::remove`, the pair is removed by shifting all of the + /// elements that follow it, preserving their relative order. + /// **This perturbs the index of all of those elements!** + /// + /// Computes in **O(n)** time (average). + pub fn shift_remove_entry(self) -> (K, V) { + self.map.shift_remove_index(self.index).unwrap() + } + + /// Remove the key, value pair stored in the map for this entry, and return the value. + /// + /// Like `Vec::swap_remove`, the pair is removed by swapping it with the + /// last element of the map and popping it off. **This perturbs + /// the position of what used to be the last element!** + /// + /// Computes in **O(1)** time (average). + pub fn swap_remove(self) -> V { + self.swap_remove_entry().1 + } + + /// Remove the key, value pair stored in the map for this entry, and return the value. + /// + /// Like `Vec::remove`, the pair is removed by shifting all of the + /// elements that follow it, preserving their relative order. + /// **This perturbs the index of all of those elements!** + /// + /// Computes in **O(n)** time (average). + pub fn shift_remove(self) -> V { + self.shift_remove_entry().1 + } +} + +impl fmt::Debug for IndexedEntry<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(stringify!(IndexedEntry)) + .field("index", &self.index) + .field("key", self.key()) + .field("value", self.get()) + .finish() + } +} + #[test] fn assert_send_sync() { fn assert_send_sync() {} assert_send_sync::>(); assert_send_sync::>(); + assert_send_sync::>(); } diff --git a/src/map/tests.rs b/src/map/tests.rs index 3d2315d3..d81d51b9 100644 --- a/src/map/tests.rs +++ b/src/map/tests.rs @@ -359,6 +359,36 @@ fn occupied_entry_key() { } } +#[test] +fn get_index_entry() { + let mut map = IndexMap::new(); + + assert!(map.get_index_entry(0).is_none()); + + map.insert(0, "0"); + map.insert(1, "1"); + map.insert(2, "2"); + map.insert(3, "3"); + + assert!(map.get_index_entry(4).is_none()); + + { + let e = map.get_index_entry(1).unwrap(); + assert_eq!(*e.key(), 1); + assert_eq!(*e.get(), "1"); + assert_eq!(e.swap_remove(), "1"); + } + + { + let mut e = map.get_index_entry(1).unwrap(); + assert_eq!(*e.key(), 3); + assert_eq!(*e.get(), "3"); + assert_eq!(e.insert("4"), "3"); + } + + assert_eq!(*map.get(&3).unwrap(), "4"); +} + #[test] fn keys() { let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; From c42f93304c1c095d4c50206d26fc8ee1f0b149c6 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 12 Jan 2024 09:42:40 -0800 Subject: [PATCH 143/236] Re-deprecate `remove`, `remove_entry`, and `take` It's a frequent point of confusion that these methods disrupt the order (by swapping the last item), but that choice was made for performance. The alternative `shift_*` removals are also disruptive to indices, and require O(n) updates instead of O(1). The deprecation notes recommend instead choosing a swap/shift method explicitly. This doesn't mean that we'll ever actually _remove_ these deprecated methods, since they are still nice to have when acting as a drop-in `HashMap` replacement, but the warning will hopefully help avoid surprises. --- src/map.rs | 22 ++++++++++++---------- src/map/core.rs | 14 ++++++++++++-- src/set.rs | 21 ++++++++++++--------- 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/src/map.rs b/src/map.rs index 4ee24d5f..f225e069 100644 --- a/src/map.rs +++ b/src/map.rs @@ -520,11 +520,12 @@ where /// Remove the key-value pair equivalent to `key` and return /// its value. /// - /// **NOTE:** This is equivalent to `.swap_remove(key)`, if you need to - /// preserve the order of the keys in the map, use `.shift_remove(key)` - /// instead. - /// - /// Computes in **O(1)** time (average). + /// **NOTE:** This is equivalent to [`.swap_remove(key)`][Self::swap_remove], replacing this + /// entry's position with the last element, and it is deprecated in favor of calling that + /// explicitly. If you need to preserve the relative order of the keys in the map, use + /// [`.shift_remove(key)`][Self::shift_remove] instead. + #[deprecated(note = "`remove` disrupts the map order -- \ + use `swap_remove` or `shift_remove` for explicit behavior.")] pub fn remove(&mut self, key: &Q) -> Option where Q: Hash + Equivalent, @@ -534,11 +535,12 @@ where /// Remove and return the key-value pair equivalent to `key`. /// - /// **NOTE:** This is equivalent to `.swap_remove_entry(key)`, if you need to - /// preserve the order of the keys in the map, use `.shift_remove_entry(key)` - /// instead. - /// - /// Computes in **O(1)** time (average). + /// **NOTE:** This is equivalent to [`.swap_remove_entry(key)`][Self::swap_remove_entry], + /// replacing this entry's position with the last element, and it is deprecated in favor of + /// calling that explicitly. If you need to preserve the relative order of the keys in the map, + /// use [`.shift_remove_entry(key)`][Self::shift_remove_entry] instead. + #[deprecated(note = "`remove_entry` disrupts the map order -- \ + use `swap_remove_entry` or `shift_remove_entry` for explicit behavior.")] pub fn remove_entry(&mut self, key: &Q) -> Option<(K, V)> where Q: Hash + Equivalent, diff --git a/src/map/core.rs b/src/map/core.rs index 4a78035c..4698b482 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -664,7 +664,12 @@ impl OccupiedEntry<'_, K, V> { /// Remove the key, value pair stored in the map for this entry, and return the value. /// - /// **NOTE:** This is equivalent to `.swap_remove()`. + /// **NOTE:** This is equivalent to [`.swap_remove()`][Self::swap_remove], replacing this + /// entry's position with the last element, and it is deprecated in favor of calling that + /// explicitly. If you need to preserve the relative order of the keys in the map, use + /// [`.shift_remove()`][Self::shift_remove] instead. + #[deprecated(note = "`remove` disrupts the map order -- \ + use `swap_remove` or `shift_remove` for explicit behavior.")] pub fn remove(self) -> V { self.swap_remove() } @@ -693,7 +698,12 @@ impl OccupiedEntry<'_, K, V> { /// Remove and return the key, value pair stored in the map for this entry /// - /// **NOTE:** This is equivalent to `.swap_remove_entry()`. + /// **NOTE:** This is equivalent to [`.swap_remove_entry()`][Self::swap_remove_entry], + /// replacing this entry's position with the last element, and it is deprecated in favor of + /// calling that explicitly. If you need to preserve the relative order of the keys in the map, + /// use [`.shift_remove_entry()`][Self::shift_remove_entry] instead. + #[deprecated(note = "`remove_entry` disrupts the map order -- \ + use `swap_remove_entry` or `shift_remove_entry` for explicit behavior.")] pub fn remove_entry(self) -> (K, V) { self.swap_remove_entry() } diff --git a/src/set.rs b/src/set.rs index c047b0be..fea1edb7 100644 --- a/src/set.rs +++ b/src/set.rs @@ -463,10 +463,12 @@ where /// Remove the value from the set, and return `true` if it was present. /// - /// **NOTE:** This is equivalent to `.swap_remove(value)`, if you want - /// to preserve the order of the values in the set, use `.shift_remove(value)`. - /// - /// Computes in **O(1)** time (average). + /// **NOTE:** This is equivalent to [`.swap_remove(value)`][Self::swap_remove], replacing this + /// value's position with the last element, and it is deprecated in favor of calling that + /// explicitly. If you need to preserve the relative order of the values in the set, use + /// [`.shift_remove(value)`][Self::shift_remove] instead. + #[deprecated(note = "`remove` disrupts the set order -- \ + use `swap_remove` or `shift_remove` for explicit behavior.")] pub fn remove(&mut self, value: &Q) -> bool where Q: Hash + Equivalent, @@ -509,11 +511,12 @@ where /// Removes and returns the value in the set, if any, that is equal to the /// given one. /// - /// **NOTE:** This is equivalent to `.swap_take(value)`, if you need to - /// preserve the order of the values in the set, use `.shift_take(value)` - /// instead. - /// - /// Computes in **O(1)** time (average). + /// **NOTE:** This is equivalent to [`.swap_take(value)`][Self::swap_take], replacing this + /// value's position with the last element, and it is deprecated in favor of calling that + /// explicitly. If you need to preserve the relative order of the values in the set, use + /// [`.shift_take(value)`][Self::shift_take] instead. + #[deprecated(note = "`take` disrupts the set order -- \ + use `swap_take` or `shift_take` for explicit behavior.")] pub fn take(&mut self, value: &Q) -> Option where Q: Hash + Equivalent, From b71b00df86a4a1a407d7051ec41e13a17a93e81a Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 12 Jan 2024 09:57:16 -0800 Subject: [PATCH 144/236] Reorder `OccupiedEntry` methods for better docs --- src/map/core.rs | 24 ++++++++++++++++++++++++ src/map/core/raw.rs | 44 ++++++++++++-------------------------------- 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/map/core.rs b/src/map/core.rs index 4698b482..601af4ea 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -707,6 +707,30 @@ impl OccupiedEntry<'_, K, V> { pub fn remove_entry(self) -> (K, V) { self.swap_remove_entry() } + + /// Remove and return the key, value pair stored in the map for this entry + /// + /// Like `Vec::swap_remove`, the pair is removed by swapping it with the + /// last element of the map and popping it off. **This perturbs + /// the position of what used to be the last element!** + /// + /// Computes in **O(1)** time (average). + pub fn swap_remove_entry(self) -> (K, V) { + let (map, index) = self.remove_index(); + map.swap_remove_finish(index) + } + + /// Remove and return the key, value pair stored in the map for this entry + /// + /// Like `Vec::remove`, the pair is removed by shifting all of the + /// elements that follow it, preserving their relative order. + /// **This perturbs the index of all of those elements!** + /// + /// Computes in **O(n)** time (average). + pub fn shift_remove_entry(self) -> (K, V) { + let (map, index) = self.remove_index(); + map.shift_remove_finish(index) + } } impl fmt::Debug for OccupiedEntry<'_, K, V> { diff --git a/src/map/core/raw.rs b/src/map/core/raw.rs index be71c9cf..0e33eca5 100644 --- a/src/map/core/raw.rs +++ b/src/map/core/raw.rs @@ -143,52 +143,32 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { &mut self.map.entries[index].value } - /// Put the new key in the occupied entry's key slot - pub(crate) fn replace_key(self) -> K { + /// Converts into a mutable reference to the entry's value in the map, + /// with a lifetime bound to the map itself. + pub fn into_mut(self) -> &'a mut V { let index = self.index(); - let old_key = &mut self.map.entries[index].key; - replace(old_key, self.key) + &mut self.map.entries[index].value } /// Return the index of the key-value pair #[inline] pub fn index(&self) -> usize { - // SAFETY: we have &mut map keep keeping the bucket stable + // SAFETY: we have `&mut map` keeping the bucket stable unsafe { *self.raw_bucket.as_ref() } } - /// Converts into a mutable reference to the entry's value in the map, - /// with a lifetime bound to the map itself. - pub fn into_mut(self) -> &'a mut V { + /// Put the new key in the occupied entry's key slot + pub(crate) fn replace_key(self) -> K { let index = self.index(); - &mut self.map.entries[index].value - } - - /// Remove and return the key, value pair stored in the map for this entry - /// - /// Like `Vec::swap_remove`, the pair is removed by swapping it with the - /// last element of the map and popping it off. **This perturbs - /// the position of what used to be the last element!** - /// - /// Computes in **O(1)** time (average). - pub fn swap_remove_entry(self) -> (K, V) { - // SAFETY: This is safe because it can only happen once (self is consumed) - // and map.indices have not been modified since entry construction - let (index, _slot) = unsafe { self.map.indices.remove(self.raw_bucket) }; - self.map.swap_remove_finish(index) + let old_key = &mut self.map.entries[index].key; + replace(old_key, self.key) } - /// Remove and return the key, value pair stored in the map for this entry - /// - /// Like `Vec::remove`, the pair is removed by shifting all of the - /// elements that follow it, preserving their relative order. - /// **This perturbs the index of all of those elements!** - /// - /// Computes in **O(n)** time (average). - pub fn shift_remove_entry(self) -> (K, V) { + /// Remove the index from indices, leaving the actual entries to the caller. + pub(super) fn remove_index(self) -> (&'a mut IndexMapCore, usize) { // SAFETY: This is safe because it can only happen once (self is consumed) // and map.indices have not been modified since entry construction let (index, _slot) = unsafe { self.map.indices.remove(self.raw_bucket) }; - self.map.shift_remove_finish(index) + (self.map, index) } } From 5fc8277300d6f2e061b0edd89a608716a703502e Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 12 Jan 2024 10:02:30 -0800 Subject: [PATCH 145/236] Move `get_index_entry` to an unconstrained `impl` --- src/map.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/map.rs b/src/map.rs index 76eee91f..6dc36188 100644 --- a/src/map.rs +++ b/src/map.rs @@ -423,18 +423,6 @@ where self.core.entry(hash, key) } - /// Get an entry in the map by index for in-place manipulation. - /// - /// Valid indices are *0 <= index < self.len()* - /// - /// Computes in **O(1)** time. - pub fn get_index_entry(&mut self, index: usize) -> Option> { - if index >= self.len() { - return None; - } - Some(IndexedEntry::new(&mut self.core, index)) - } - /// Return `true` if an equivalent to `key` exists in the map. /// /// Computes in **O(1)** time (average). @@ -911,6 +899,18 @@ impl IndexMap { self.as_entries_mut().get_mut(index).map(Bucket::ref_mut) } + /// Get an entry in the map by index for in-place manipulation. + /// + /// Valid indices are *0 <= index < self.len()* + /// + /// Computes in **O(1)** time. + pub fn get_index_entry(&mut self, index: usize) -> Option> { + if index >= self.len() { + return None; + } + Some(IndexedEntry::new(&mut self.core, index)) + } + /// Returns a slice of key-value pairs in the given range of indices. /// /// Valid indices are *0 <= index < self.len()* From 82bc7b3667d02a46fc4ed90f7129e5cdf3b55b1a Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 12 Jan 2024 16:54:56 -0800 Subject: [PATCH 146/236] Update the remove-order note in the README --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bf869323..1363cfdb 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,9 @@ was indexmap, a hash table that has following properties: - Order is **independent of hash function** and hash values of keys. - Fast to iterate. - Indexed in compact space. -- Preserves insertion order **as long** as you don't call `.remove()`. +- Preserves insertion order **as long** as you don't call `.remove()`, + `.swap_remove()`, or other methods that explicitly change order. + The alternate `.shift_remove()` does preserve relative order. - Uses hashbrown for the inner table, just like Rust's libstd `HashMap` does. ## Performance From 1e873ccee5ed171ef309c89e6701c41e7c851921 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Mon, 15 Jan 2024 14:30:52 -0800 Subject: [PATCH 147/236] Use more rustdoc auto-links --- src/lib.rs | 16 +++------ src/macros.rs | 4 +-- src/map.rs | 56 ++++++++++++++++--------------- src/map/core.rs | 30 ++++++++--------- src/map/core/raw.rs | 6 ++-- src/map/iter.rs | 79 +++++++++++++++----------------------------- src/map/serde_seq.rs | 14 ++++---- src/map/slice.rs | 6 ++-- src/mutable_keys.rs | 6 ++-- src/rayon/map.rs | 65 ++++++++++++++---------------------- src/rayon/mod.rs | 2 ++ src/rayon/set.rs | 59 ++++++++++++--------------------- src/serde.rs | 2 ++ src/set.rs | 34 ++++++++++--------- src/set/iter.rs | 53 +++++++++-------------------- src/set/slice.rs | 6 ++-- 16 files changed, 177 insertions(+), 261 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 45d8b2e9..b88c1bce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,10 +9,6 @@ //! [`IndexSet`] is a corresponding hash set using the same implementation and //! with similar properties. //! -//! [`IndexMap`]: map/struct.IndexMap.html -//! [`IndexSet`]: set/struct.IndexSet.html -//! -//! //! ### Highlights //! //! [`IndexMap`] and [`IndexSet`] are drop-in compatible with the std `HashMap` @@ -55,7 +51,8 @@ //! //! ### Alternate Hashers //! -//! [`IndexMap`] and [`IndexSet`] have a default hasher type `S = RandomState`, +//! [`IndexMap`] and [`IndexSet`] have a default hasher type +//! [`S = RandomState`][std::collections::hash_map::RandomState], //! just like the standard `HashMap` and `HashSet`, which is resistant to //! HashDoS attacks but not the most performant. Type aliases can make it easier //! to use alternate hashers: @@ -94,14 +91,11 @@ //! //! - Creating maps and sets using [`new`][IndexMap::new] and //! [`with_capacity`][IndexMap::with_capacity] is unavailable without `std`. -//! Use methods [`IndexMap::default`][def], -//! [`with_hasher`][IndexMap::with_hasher], +//! Use methods [`IndexMap::default`], [`with_hasher`][IndexMap::with_hasher], //! [`with_capacity_and_hasher`][IndexMap::with_capacity_and_hasher] instead. //! A no-std compatible hasher will be needed as well, for example //! from the crate `twox-hash`. //! - Macros [`indexmap!`] and [`indexset!`] are unavailable without `std`. -//! -//! [def]: map/struct.IndexMap.html#impl-Default #![cfg_attr(docsrs, feature(doc_cfg))] @@ -118,7 +112,6 @@ mod arbitrary; mod macros; mod mutable_keys; #[cfg(feature = "serde")] -#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] mod serde; mod util; @@ -128,7 +121,6 @@ pub mod set; // Placed after `map` and `set` so new `rayon` methods on the types // are documented after the "normal" methods. #[cfg(feature = "rayon")] -#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))] mod rayon; #[cfg(feature = "rustc-rayon")] @@ -220,7 +212,7 @@ trait Entries { F: FnOnce(&mut [Self::Entry]); } -/// The error type for `try_reserve` methods. +/// The error type for [`try_reserve`][IndexMap::try_reserve] methods. #[derive(Clone, PartialEq, Eq, Debug)] pub struct TryReserveError { kind: TryReserveErrorKind, diff --git a/src/macros.rs b/src/macros.rs index 5317f1c9..b347de22 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,7 +1,7 @@ #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] #[macro_export] -/// Create an `IndexMap` from a list of key-value pairs +/// Create an [`IndexMap`][crate::IndexMap] from a list of key-value pairs /// /// ## Example /// @@ -38,7 +38,7 @@ macro_rules! indexmap { #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] #[macro_export] -/// Create an `IndexSet` from a list of values +/// Create an [`IndexSet`][crate::IndexSet] from a list of values /// /// ## Example /// diff --git a/src/map.rs b/src/map.rs index 0fee098f..c7e2f4d5 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1,4 +1,4 @@ -//! `IndexMap` is a hash table where the iteration order of the key-value +//! [`IndexMap`] is a hash table where the iteration order of the key-value //! pairs is independent of the hash values of the keys. mod core; @@ -12,7 +12,7 @@ pub mod serde_seq; #[cfg(test)] mod tests; -pub use self::core::{Entry, OccupiedEntry, VacantEntry}; +pub use self::core::{Entry, IndexedEntry, OccupiedEntry, VacantEntry}; pub use self::iter::{ Drain, IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Values, ValuesMut, }; @@ -32,15 +32,16 @@ use alloc::vec::Vec; #[cfg(feature = "std")] use std::collections::hash_map::RandomState; -use self::core::{IndexMapCore, IndexedEntry}; +use self::core::IndexMapCore; use crate::util::{third, try_simplify_range}; use crate::{Bucket, Entries, Equivalent, HashValue, TryReserveError}; /// A hash table where the iteration order of the key-value pairs is independent /// of the hash values of the keys. /// -/// The interface is closely compatible with the standard `HashMap`, but also -/// has additional features. +/// The interface is closely compatible with the standard +/// [`HashMap`][std::collections::HashMap], +/// but also has additional features. /// /// # Order /// @@ -51,7 +52,8 @@ use crate::{Bucket, Entries, Equivalent, HashValue, TryReserveError}; /// All iterators traverse the map in *the order*. /// /// The insertion order is preserved, with **notable exceptions** like the -/// `.remove()` or `.swap_remove()` methods. Methods such as `.sort_by()` of +/// [`.remove()`][Self::remove] or [`.swap_remove()`][Self::swap_remove] methods. +/// Methods such as [`.sort_by()`][Self::sort_by] of /// course result in a new order, depending on the sorting order. /// /// # Indices @@ -281,7 +283,7 @@ impl IndexMap { /// Clears the `IndexMap` in the given index range, returning those /// key-value pairs as a drain iterator. /// - /// The range may be any type that implements `RangeBounds`, + /// The range may be any type that implements [`RangeBounds`], /// including all of the `std::ops::Range*` types, or even a tuple pair of /// `Bound` start and end values. To drain the map entirely, use `RangeFull` /// like `map.drain(..)`. @@ -390,8 +392,9 @@ where /// /// Computes in **O(1)** time (amortized average). /// - /// See also [`entry`](#method.entry) if you you want to insert *or* modify - /// or if you need to get the index of the corresponding key-value pair. + /// See also [`entry`][Self::entry] if you 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 { self.insert_full(key, value).1 } @@ -407,8 +410,7 @@ where /// /// Computes in **O(1)** time (amortized average). /// - /// See also [`entry`](#method.entry) if you you want to insert *or* modify - /// or if you need to get the index of the corresponding key-value pair. + /// See also [`entry`][Self::entry] if you 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) @@ -551,7 +553,7 @@ where /// Remove the key-value pair equivalent to `key` and return /// its value. /// - /// Like `Vec::swap_remove`, the pair is removed by swapping it with the + /// Like [`Vec::swap_remove`], the pair is removed by swapping it with the /// last element of the map and popping it off. **This perturbs /// the position of what used to be the last element!** /// @@ -567,7 +569,7 @@ where /// Remove and return the key-value pair equivalent to `key`. /// - /// Like `Vec::swap_remove`, the pair is removed by swapping it with the + /// Like [`Vec::swap_remove`], the pair is removed by swapping it with the /// last element of the map and popping it off. **This perturbs /// the position of what used to be the last element!** /// @@ -587,7 +589,7 @@ where /// Remove the key-value pair equivalent to `key` and return it and /// the index it had. /// - /// Like `Vec::swap_remove`, the pair is removed by swapping it with the + /// Like [`Vec::swap_remove`], the pair is removed by swapping it with the /// last element of the map and popping it off. **This perturbs /// the position of what used to be the last element!** /// @@ -608,7 +610,7 @@ where /// Remove the key-value pair equivalent to `key` and return /// its value. /// - /// Like `Vec::remove`, the pair is removed by shifting all of the + /// Like [`Vec::remove`], the pair is removed by shifting all of the /// elements that follow it, preserving their relative order. /// **This perturbs the index of all of those elements!** /// @@ -624,7 +626,7 @@ where /// Remove and return the key-value pair equivalent to `key`. /// - /// Like `Vec::remove`, the pair is removed by shifting all of the + /// Like [`Vec::remove`], the pair is removed by shifting all of the /// elements that follow it, preserving their relative order. /// **This perturbs the index of all of those elements!** /// @@ -644,7 +646,7 @@ where /// Remove the key-value pair equivalent to `key` and return it and /// the index it had. /// - /// Like `Vec::remove`, the pair is removed by shifting all of the + /// Like [`Vec::remove`], the pair is removed by shifting all of the /// elements that follow it, preserving their relative order. /// **This perturbs the index of all of those elements!** /// @@ -967,7 +969,7 @@ impl IndexMap { /// /// Valid indices are *0 <= index < self.len()* /// - /// Like `Vec::swap_remove`, the pair is removed by swapping it with the + /// Like [`Vec::swap_remove`], the pair is removed by swapping it with the /// last element of the map and popping it off. **This perturbs /// the position of what used to be the last element!** /// @@ -980,7 +982,7 @@ impl IndexMap { /// /// Valid indices are *0 <= index < self.len()* /// - /// Like `Vec::remove`, the pair is removed by shifting all of the + /// Like [`Vec::remove`], the pair is removed by shifting all of the /// elements that follow it, preserving their relative order. /// **This perturbs the index of all of those elements!** /// @@ -1010,7 +1012,7 @@ impl IndexMap { } } -/// Access `IndexMap` values corresponding to a key. +/// Access [`IndexMap`] values corresponding to a key. /// /// # Examples /// @@ -1048,7 +1050,7 @@ where } } -/// Access `IndexMap` values corresponding to a key. +/// Access [`IndexMap`] values corresponding to a key. /// /// Mutable indexing allows changing / updating values of key-value /// pairs that are already present. @@ -1091,7 +1093,7 @@ where } } -/// Access `IndexMap` values at indexed positions. +/// Access [`IndexMap`] values at indexed positions. /// /// See [`Index for Keys`][keys] to access a map's keys instead. /// @@ -1136,12 +1138,12 @@ impl Index for IndexMap { } } -/// Access `IndexMap` values at indexed positions. +/// Access [`IndexMap`] values at indexed positions. /// /// Mutable indexing allows changing / updating indexed values /// that are already present. /// -/// You can **not** insert new values with index syntax, use `.insert()`. +/// You can **not** insert new values with index syntax -- use [`.insert()`][IndexMap::insert]. /// /// # Examples /// @@ -1185,7 +1187,7 @@ where /// iterable. /// /// `from_iter` uses the same logic as `extend`. See - /// [`extend`](#method.extend) for more details. + /// [`extend`][IndexMap::extend] for more details. fn from_iter>(iterable: I) -> Self { let iter = iterable.into_iter(); let (low, _) = iter.size_hint(); @@ -1222,7 +1224,7 @@ where { /// Extend the map with all key-value pairs in the iterable. /// - /// This is equivalent to calling [`insert`](#method.insert) for each of + /// This is equivalent to calling [`insert`][IndexMap::insert] for each of /// them in order, which means that for keys that already existed /// in the map, their value is updated but it keeps the existing order. /// @@ -1266,7 +1268,7 @@ impl Default for IndexMap where S: Default, { - /// Return an empty `IndexMap` + /// Return an empty [`IndexMap`] fn default() -> Self { Self::with_capacity_and_hasher(0, S::default()) } diff --git a/src/map/core.rs b/src/map/core.rs index 887c2976..e6e70e50 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -545,8 +545,8 @@ impl IndexMapCore { } } -/// Entry for an existing key-value pair or a vacant location to -/// insert one. +/// Entry for an existing key-value pair in an [`IndexMap`][crate::IndexMap] +/// or a vacant location to insert one. pub enum Entry<'a, K, V> { /// Existing slot with equivalent key. Occupied(OccupiedEntry<'a, K, V>), @@ -676,7 +676,7 @@ impl OccupiedEntry<'_, K, V> { /// Remove the key, value pair stored in the map for this entry, and return the value. /// - /// Like `Vec::swap_remove`, the pair is removed by swapping it with the + /// Like [`Vec::swap_remove`], the pair is removed by swapping it with the /// last element of the map and popping it off. **This perturbs /// the position of what used to be the last element!** /// @@ -687,7 +687,7 @@ impl OccupiedEntry<'_, K, V> { /// Remove the key, value pair stored in the map for this entry, and return the value. /// - /// Like `Vec::remove`, the pair is removed by shifting all of the + /// Like [`Vec::remove`], the pair is removed by shifting all of the /// elements that follow it, preserving their relative order. /// **This perturbs the index of all of those elements!** /// @@ -710,7 +710,7 @@ impl OccupiedEntry<'_, K, V> { /// Remove and return the key, value pair stored in the map for this entry /// - /// Like `Vec::swap_remove`, the pair is removed by swapping it with the + /// Like [`Vec::swap_remove`], the pair is removed by swapping it with the /// last element of the map and popping it off. **This perturbs /// the position of what used to be the last element!** /// @@ -722,7 +722,7 @@ impl OccupiedEntry<'_, K, V> { /// Remove and return the key, value pair stored in the map for this entry /// - /// Like `Vec::remove`, the pair is removed by shifting all of the + /// Like [`Vec::remove`], the pair is removed by shifting all of the /// elements that follow it, preserving their relative order. /// **This perturbs the index of all of those elements!** /// @@ -742,10 +742,8 @@ impl fmt::Debug for OccupiedEntry<'_, K, V> { } } -/// A view into a vacant entry in a `IndexMap`. +/// A view into a vacant entry in an [`IndexMap`][crate::IndexMap]. /// It is part of the [`Entry`] enum. -/// -/// [`Entry`]: enum.Entry.html pub struct VacantEntry<'a, K, V> { map: &'a mut IndexMapCore, hash: HashValue, @@ -788,9 +786,9 @@ impl fmt::Debug for VacantEntry<'_, K, V> { } } -/// A view into an occupied entry in a `IndexMap` obtained by index. +/// A view into an occupied entry in an [`IndexMap`][crate::IndexMap] obtained by index. /// -/// This struct is constructed from the `get_index_entry` method on `IndexMap`. +/// This `struct` is created from the [`get_index_entry`][crate::IndexMap::get_index_entry] method. pub struct IndexedEntry<'a, K, V> { map: &'a mut IndexMapCore, // We have a mutable reference to the map, which keeps the index @@ -816,7 +814,7 @@ impl<'a, K, V> IndexedEntry<'a, K, V> { /// Gets a mutable reference to the entry's value in the map. /// /// If you need a reference which may outlive the destruction of the - /// `IndexedEntry` value, see `into_mut`. + /// `IndexedEntry` value, see [`into_mut`][Self::into_mut]. pub fn get_mut(&mut self) -> &mut V { &mut self.map.entries[self.index].value } @@ -840,7 +838,7 @@ impl<'a, K, V> IndexedEntry<'a, K, V> { /// Remove and return the key, value pair stored in the map for this entry /// - /// Like `Vec::swap_remove`, the pair is removed by swapping it with the + /// Like [`Vec::swap_remove`], the pair is removed by swapping it with the /// last element of the map and popping it off. **This perturbs /// the position of what used to be the last element!** /// @@ -851,7 +849,7 @@ impl<'a, K, V> IndexedEntry<'a, K, V> { /// Remove and return the key, value pair stored in the map for this entry /// - /// Like `Vec::remove`, the pair is removed by shifting all of the + /// Like [`Vec::remove`], the pair is removed by shifting all of the /// elements that follow it, preserving their relative order. /// **This perturbs the index of all of those elements!** /// @@ -862,7 +860,7 @@ impl<'a, K, V> IndexedEntry<'a, K, V> { /// Remove the key, value pair stored in the map for this entry, and return the value. /// - /// Like `Vec::swap_remove`, the pair is removed by swapping it with the + /// Like [`Vec::swap_remove`], the pair is removed by swapping it with the /// last element of the map and popping it off. **This perturbs /// the position of what used to be the last element!** /// @@ -873,7 +871,7 @@ impl<'a, K, V> IndexedEntry<'a, K, V> { /// Remove the key, value pair stored in the map for this entry, and return the value. /// - /// Like `Vec::remove`, the pair is removed by shifting all of the + /// Like [`Vec::remove`], the pair is removed by shifting all of the /// elements that follow it, preserving their relative order. /// **This perturbs the index of all of those elements!** /// diff --git a/src/map/core/raw.rs b/src/map/core/raw.rs index 0e33eca5..45b03a3b 100644 --- a/src/map/core/raw.rs +++ b/src/map/core/raw.rs @@ -102,10 +102,8 @@ impl IndexMapCore { } } -/// A view into an occupied entry in a `IndexMap`. +/// A view into an occupied entry in an [`IndexMap`][crate::IndexMap]. /// It is part of the [`Entry`] enum. -/// -/// [`Entry`]: enum.Entry.html // SAFETY: The lifetime of the map reference also constrains the raw bucket, // which is essentially a raw pointer into the map indices. pub struct OccupiedEntry<'a, K, V> { @@ -137,7 +135,7 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { /// Gets a mutable reference to the entry's value in the map. /// /// If you need a reference which may outlive the destruction of the - /// `Entry` value, see `into_mut`. + /// [`Entry`] value, see [`into_mut`][Self::into_mut]. pub fn get_mut(&mut self) -> &mut V { let index = self.index(); &mut self.map.entries[index].value diff --git a/src/map/iter.rs b/src/map/iter.rs index 97d4473e..2039de9c 100644 --- a/src/map/iter.rs +++ b/src/map/iter.rs @@ -33,13 +33,10 @@ impl IntoIterator for IndexMap { } } -/// An iterator over the entries of a `IndexMap`. +/// An iterator over the entries of an [`IndexMap`]. /// -/// This `struct` is created by the [`iter`] method on [`IndexMap`]. See its -/// documentation for more. -/// -/// [`iter`]: struct.IndexMap.html#method.iter -/// [`IndexMap`]: struct.IndexMap.html +/// This `struct` is created by the [`IndexMap::iter`] method. +/// See its documentation for more. pub struct Iter<'a, K, V> { iter: slice::Iter<'a, Bucket>, } @@ -96,13 +93,10 @@ impl Default for Iter<'_, K, V> { } } -/// A mutable iterator over the entries of a `IndexMap`. +/// A mutable iterator over the entries of an [`IndexMap`]. /// -/// This `struct` is created by the [`iter_mut`] method on [`IndexMap`]. See its -/// documentation for more. -/// -/// [`iter_mut`]: struct.IndexMap.html#method.iter_mut -/// [`IndexMap`]: struct.IndexMap.html +/// This `struct` is created by the [`IndexMap::iter_mut`] method. +/// See its documentation for more. pub struct IterMut<'a, K, V> { iter: slice::IterMut<'a, Bucket>, } @@ -160,13 +154,10 @@ impl Default for IterMut<'_, K, V> { } } -/// An owning iterator over the entries of a `IndexMap`. +/// An owning iterator over the entries of an [`IndexMap`]. /// -/// This `struct` is created by the [`into_iter`] method on [`IndexMap`] -/// (provided by the `IntoIterator` trait). See its documentation for more. -/// -/// [`into_iter`]: struct.IndexMap.html#method.into_iter -/// [`IndexMap`]: struct.IndexMap.html +/// This `struct` is created by the [`IndexMap::into_iter`] method +/// (provided by the [`IntoIterator`] trait). See its documentation for more. pub struct IntoIter { iter: vec::IntoIter>, } @@ -222,13 +213,10 @@ impl Default for IntoIter { } } -/// A draining iterator over the entries of a `IndexMap`. -/// -/// This `struct` is created by the [`drain`] method on [`IndexMap`]. See its -/// documentation for more. +/// A draining iterator over the entries of an [`IndexMap`]. /// -/// [`drain`]: struct.IndexMap.html#method.drain -/// [`IndexMap`]: struct.IndexMap.html +/// This `struct` is created by the [`IndexMap::drain`] method. +/// See its documentation for more. pub struct Drain<'a, K, V> { iter: vec::Drain<'a, Bucket>, } @@ -269,13 +257,10 @@ impl fmt::Debug for Drain<'_, K, V> { } } -/// An iterator over the keys of a `IndexMap`. -/// -/// This `struct` is created by the [`keys`] method on [`IndexMap`]. See its -/// documentation for more. +/// An iterator over the keys of an [`IndexMap`]. /// -/// [`keys`]: struct.IndexMap.html#method.keys -/// [`IndexMap`]: struct.IndexMap.html +/// This `struct` is created by the [`IndexMap::keys`] method. +/// See its documentation for more. pub struct Keys<'a, K, V> { iter: slice::Iter<'a, Bucket>, } @@ -327,7 +312,7 @@ impl Default for Keys<'_, K, V> { } } -/// Access `IndexMap` keys at indexed positions. +/// Access [`IndexMap`] keys at indexed positions. /// /// While [`Index for IndexMap`][values] accesses a map's values, /// indexing through [`IndexMap::keys`] offers an alternative to access a map's @@ -394,13 +379,10 @@ impl<'a, K, V> Index for Keys<'a, K, V> { } } -/// An owning iterator over the keys of a `IndexMap`. +/// An owning iterator over the keys of an [`IndexMap`]. /// -/// This `struct` is created by the [`into_keys`] method on [`IndexMap`]. +/// This `struct` is created by the [`IndexMap::into_keys`] method. /// See its documentation for more. -/// -/// [`IndexMap`]: struct.IndexMap.html -/// [`into_keys`]: struct.IndexMap.html#method.into_keys pub struct IntoKeys { iter: vec::IntoIter>, } @@ -446,13 +428,10 @@ impl Default for IntoKeys { } } -/// An iterator over the values of a `IndexMap`. +/// An iterator over the values of an [`IndexMap`]. /// -/// This `struct` is created by the [`values`] method on [`IndexMap`]. See its -/// documentation for more. -/// -/// [`values`]: struct.IndexMap.html#method.values -/// [`IndexMap`]: struct.IndexMap.html +/// This `struct` is created by the [`IndexMap::values`] method. +/// See its documentation for more. pub struct Values<'a, K, V> { iter: slice::Iter<'a, Bucket>, } @@ -504,13 +483,10 @@ impl Default for Values<'_, K, V> { } } -/// A mutable iterator over the values of a `IndexMap`. +/// A mutable iterator over the values of an [`IndexMap`]. /// -/// This `struct` is created by the [`values_mut`] method on [`IndexMap`]. See its -/// documentation for more. -/// -/// [`values_mut`]: struct.IndexMap.html#method.values_mut -/// [`IndexMap`]: struct.IndexMap.html +/// This `struct` is created by the [`IndexMap::values_mut`] method. +/// See its documentation for more. pub struct ValuesMut<'a, K, V> { iter: slice::IterMut<'a, Bucket>, } @@ -556,13 +532,10 @@ impl Default for ValuesMut<'_, K, V> { } } -/// An owning iterator over the values of a `IndexMap`. +/// An owning iterator over the values of an [`IndexMap`]. /// -/// This `struct` is created by the [`into_values`] method on [`IndexMap`]. +/// This `struct` is created by the [`IndexMap::into_values`] method. /// See its documentation for more. -/// -/// [`IndexMap`]: struct.IndexMap.html -/// [`into_values`]: struct.IndexMap.html#method.into_values pub struct IntoValues { iter: vec::IntoIter>, } diff --git a/src/map/serde_seq.rs b/src/map/serde_seq.rs index f10aa578..4282cb6e 100644 --- a/src/map/serde_seq.rs +++ b/src/map/serde_seq.rs @@ -1,4 +1,4 @@ -//! Functions to serialize and deserialize an `IndexMap` as an ordered sequence. +//! Functions to serialize and deserialize an [`IndexMap`] as an ordered sequence. //! //! The default `serde` implementation serializes `IndexMap` as a normal map, //! but there is no guarantee that serialization formats will preserve the order @@ -29,7 +29,7 @@ use crate::map::Slice as MapSlice; use crate::set::Slice as SetSlice; use crate::IndexMap; -/// Serializes a `map::Slice` as an ordered sequence. +/// Serializes a [`map::Slice`][MapSlice] as an ordered sequence. /// /// This behaves like [`crate::map::serde_seq`] for `IndexMap`, serializing a sequence /// of `(key, value)` pairs, rather than as a map that might not preserve order. @@ -46,7 +46,7 @@ where } } -/// Serializes a `set::Slice` as an ordered sequence. +/// Serializes a [`set::Slice`][SetSlice] as an ordered sequence. impl Serialize for SetSlice where T: Serialize, @@ -59,9 +59,9 @@ where } } -/// Serializes an `IndexMap` as an ordered sequence. +/// Serializes an [`IndexMap`] as an ordered sequence. /// -/// This function may be used in a field attribute for deriving `Serialize`: +/// This function may be used in a field attribute for deriving [`Serialize`]: /// /// ``` /// # use indexmap::IndexMap; @@ -113,9 +113,9 @@ where } } -/// Deserializes an `IndexMap` from an ordered sequence. +/// Deserializes an [`IndexMap`] from an ordered sequence. /// -/// This function may be used in a field attribute for deriving `Deserialize`: +/// This function may be used in a field attribute for deriving [`Deserialize`]: /// /// ``` /// # use indexmap::IndexMap; diff --git a/src/map/slice.rs b/src/map/slice.rs index ebab208c..ce001ee8 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -11,13 +11,13 @@ use core::fmt; use core::hash::{Hash, Hasher}; use core::ops::{self, Bound, Index, IndexMut, RangeBounds}; -/// A dynamically-sized slice of key-value pairs in an `IndexMap`. +/// A dynamically-sized slice of key-value pairs in an [`IndexMap`]. /// /// This supports indexed operations much like a `[(K, V)]` slice, /// but not any hashed operations on the map keys. /// -/// Unlike `IndexMap`, `Slice` does consider the order for `PartialEq` -/// and `Eq`, and it also implements `PartialOrd`, `Ord`, and `Hash`. +/// Unlike `IndexMap`, `Slice` does consider the order for [`PartialEq`] +/// and [`Eq`], and it also implements [`PartialOrd`], [`Ord`], and [`Hash`]. #[repr(transparent)] pub struct Slice { pub(crate) entries: [Bucket], diff --git a/src/mutable_keys.rs b/src/mutable_keys.rs index 7efc779b..478622c6 100644 --- a/src/mutable_keys.rs +++ b/src/mutable_keys.rs @@ -2,7 +2,7 @@ use core::hash::{BuildHasher, Hash}; use super::{Bucket, Entries, Equivalent, IndexMap}; -/// Opt-in mutable access to keys. +/// Opt-in mutable access to [`IndexMap`] keys. /// /// These methods expose `&mut K`, mutable references to the key as it is stored /// in the map. @@ -11,7 +11,7 @@ use super::{Bucket, Entries, Equivalent, IndexMap}; /// /// If keys are modified erroneously, you can no longer look them up. /// This is sound (memory safe) but a logical error hazard (just like -/// implementing PartialEq, Eq, or Hash incorrectly would be). +/// implementing `PartialEq`, `Eq`, or `Hash` incorrectly would be). /// /// `use` this trait to enable its methods for `IndexMap`. /// @@ -51,7 +51,7 @@ pub trait MutableKeys: private::Sealed { /// Opt-in mutable access to keys. /// -/// See [`MutableKeys`](trait.MutableKeys.html) for more information. +/// See [`MutableKeys`] for more information. impl MutableKeys for IndexMap where K: Eq + Hash, diff --git a/src/rayon/map.rs b/src/rayon/map.rs index d5325f2e..4613e7bd 100644 --- a/src/rayon/map.rs +++ b/src/rayon/map.rs @@ -1,4 +1,4 @@ -//! Parallel iterator types for `IndexMap` with [rayon](https://docs.rs/rayon/1.0/rayon). +//! Parallel iterator types for [`IndexMap`] with [`rayon`][::rayon]. //! //! You will rarely need to interact with this module directly unless you need to name one of the //! iterator types. @@ -49,13 +49,10 @@ where } } -/// A parallel owning iterator over the entries of a `IndexMap`. +/// A parallel owning iterator over the entries of an [`IndexMap`]. /// -/// This `struct` is created by the [`into_par_iter`] method on [`IndexMap`] -/// (provided by rayon's `IntoParallelIterator` trait). See its documentation for more. -/// -/// [`into_par_iter`]: ../struct.IndexMap.html#method.into_par_iter -/// [`IndexMap`]: ../struct.IndexMap.html +/// This `struct` is created by the [`IndexMap::into_par_iter`] method +/// (provided by rayon's [`IntoParallelIterator`] trait). See its documentation for more. pub struct IntoParIter { entries: Vec>, } @@ -107,13 +104,12 @@ where } } -/// A parallel iterator over the entries of a `IndexMap`. +/// A parallel iterator over the entries of an [`IndexMap`]. /// -/// This `struct` is created by the [`par_iter`] method on [`IndexMap`] -/// (provided by rayon's `IntoParallelRefIterator` trait). See its documentation for more. +/// This `struct` is created by the [`IndexMap::par_iter`] method +/// (provided by rayon's [`IntoParallelRefIterator`] trait). See its documentation for more. /// -/// [`par_iter`]: ../struct.IndexMap.html#method.par_iter -/// [`IndexMap`]: ../struct.IndexMap.html +/// [`IndexMap::par_iter`]: ../struct.IndexMap.html#method.par_iter pub struct ParIter<'a, K, V> { entries: &'a [Bucket], } @@ -171,13 +167,12 @@ where } } -/// A parallel mutable iterator over the entries of a `IndexMap`. +/// A parallel mutable iterator over the entries of an [`IndexMap`]. /// -/// This `struct` is created by the [`par_iter_mut`] method on [`IndexMap`] -/// (provided by rayon's `IntoParallelRefMutIterator` trait). See its documentation for more. +/// This `struct` is created by the [`IndexMap::par_iter_mut`] method +/// (provided by rayon's [`IntoParallelRefMutIterator`] trait). See its documentation for more. /// -/// [`par_iter_mut`]: ../struct.IndexMap.html#method.par_iter_mut -/// [`IndexMap`]: ../struct.IndexMap.html +/// [`IndexMap::par_iter_mut`]: ../struct.IndexMap.html#method.par_iter_mut pub struct ParIterMut<'a, K, V> { entries: &'a mut [Bucket], } @@ -214,13 +209,12 @@ where } } -/// A parallel draining iterator over the entries of a `IndexMap`. +/// A parallel draining iterator over the entries of an [`IndexMap`]. /// -/// This `struct` is created by the [`par_drain`] method on [`IndexMap`] -/// (provided by rayon's `ParallelDrainRange` trait). See its documentation for more. +/// This `struct` is created by the [`IndexMap::par_drain`] method +/// (provided by rayon's [`ParallelDrainRange`] trait). See its documentation for more. /// -/// [`par_drain`]: ../struct.IndexMap.html#method.par_drain -/// [`IndexMap`]: ../struct.IndexMap.html +/// [`IndexMap::par_drain`]: ../struct.IndexMap.html#method.par_drain pub struct ParDrain<'a, K: Send, V: Send> { entries: rayon::vec::Drain<'a, Bucket>, } @@ -318,13 +312,10 @@ where } } -/// A parallel iterator over the keys of a `IndexMap`. -/// -/// This `struct` is created by the [`par_keys`] method on [`IndexMap`]. See its -/// documentation for more. +/// A parallel iterator over the keys of an [`IndexMap`]. /// -/// [`par_keys`]: ../struct.IndexMap.html#method.par_keys -/// [`IndexMap`]: ../struct.IndexMap.html +/// This `struct` is created by the [`IndexMap::par_keys`] method. +/// See its documentation for more. pub struct ParKeys<'a, K, V> { entries: &'a [Bucket], } @@ -352,13 +343,10 @@ impl IndexedParallelIterator for ParKeys<'_, K, V> { indexed_parallel_iterator_methods!(Bucket::key_ref); } -/// A parallel iterator over the values of a `IndexMap`. +/// A parallel iterator over the values of an [`IndexMap`]. /// -/// This `struct` is created by the [`par_values`] method on [`IndexMap`]. See its -/// documentation for more. -/// -/// [`par_values`]: ../struct.IndexMap.html#method.par_values -/// [`IndexMap`]: ../struct.IndexMap.html +/// This `struct` is created by the [`IndexMap::par_values`] method. +/// See its documentation for more. pub struct ParValues<'a, K, V> { entries: &'a [Bucket], } @@ -507,13 +495,10 @@ where } } -/// A parallel mutable iterator over the values of a `IndexMap`. -/// -/// This `struct` is created by the [`par_values_mut`] method on [`IndexMap`]. See its -/// documentation for more. +/// A parallel mutable iterator over the values of an [`IndexMap`]. /// -/// [`par_values_mut`]: ../struct.IndexMap.html#method.par_values_mut -/// [`IndexMap`]: ../struct.IndexMap.html +/// This `struct` is created by the [`IndexMap::par_values_mut`] method. +/// See its documentation for more. pub struct ParValuesMut<'a, K, V> { entries: &'a mut [Bucket], } diff --git a/src/rayon/mod.rs b/src/rayon/mod.rs index ebb1ac2d..1d21569c 100644 --- a/src/rayon/mod.rs +++ b/src/rayon/mod.rs @@ -1,3 +1,5 @@ +#![cfg_attr(docsrs, doc(cfg(feature = "rayon")))] + use rayon::prelude::*; use alloc::collections::LinkedList; diff --git a/src/rayon/set.rs b/src/rayon/set.rs index 0fc478ef..db6a5f03 100644 --- a/src/rayon/set.rs +++ b/src/rayon/set.rs @@ -1,4 +1,4 @@ -//! Parallel iterator types for `IndexSet` with [rayon](https://docs.rs/rayon/1.0/rayon). +//! Parallel iterator types for [`IndexSet`] with [rayon][::rayon]. //! //! You will rarely need to interact with this module directly unless you need to name one of the //! iterator types. @@ -48,13 +48,10 @@ where } } -/// A parallel owning iterator over the items of a `IndexSet`. +/// A parallel owning iterator over the items of an [`IndexSet`]. /// -/// This `struct` is created by the [`into_par_iter`] method on [`IndexSet`] -/// (provided by rayon's `IntoParallelIterator` trait). See its documentation for more. -/// -/// [`IndexSet`]: ../struct.IndexSet.html -/// [`into_par_iter`]: ../struct.IndexSet.html#method.into_par_iter +/// This `struct` is created by the [`IndexSet::into_par_iter`] method +/// (provided by rayon's [`IntoParallelIterator`] trait). See its documentation for more. pub struct IntoParIter { entries: Vec>, } @@ -104,13 +101,12 @@ where } } -/// A parallel iterator over the items of a `IndexSet`. +/// A parallel iterator over the items of an [`IndexSet`]. /// -/// This `struct` is created by the [`par_iter`] method on [`IndexSet`] -/// (provided by rayon's `IntoParallelRefIterator` trait). See its documentation for more. +/// This `struct` is created by the [`IndexSet::par_iter`] method +/// (provided by rayon's [`IntoParallelRefIterator`] trait). See its documentation for more. /// -/// [`IndexSet`]: ../struct.IndexSet.html -/// [`par_iter`]: ../struct.IndexSet.html#method.par_iter +/// [`IndexSet::par_iter`]: ../struct.IndexSet.html#method.par_iter pub struct ParIter<'a, T> { entries: &'a [Bucket], } @@ -152,13 +148,12 @@ where } } -/// A parallel draining iterator over the items of a `IndexSet`. +/// A parallel draining iterator over the items of an [`IndexSet`]. /// -/// This `struct` is created by the [`par_drain`] method on [`IndexSet`] -/// (provided by rayon's `ParallelDrainRange` trait). See its documentation for more. +/// This `struct` is created by the [`IndexSet::par_drain`] method +/// (provided by rayon's [`ParallelDrainRange`] trait). See its documentation for more. /// -/// [`par_drain`]: ../struct.IndexSet.html#method.par_drain -/// [`IndexSet`]: ../struct.IndexSet.html +/// [`IndexSet::par_drain`]: ../struct.IndexSet.html#method.par_drain pub struct ParDrain<'a, T: Send> { entries: rayon::vec::Drain<'a, Bucket>, } @@ -294,13 +289,10 @@ where } } -/// A parallel iterator producing elements in the difference of `IndexSet`s. +/// A parallel iterator producing elements in the difference of [`IndexSet`]s. /// -/// This `struct` is created by the [`par_difference`] method on [`IndexSet`]. +/// This `struct` is created by the [`IndexSet::par_difference`] method. /// See its documentation for more. -/// -/// [`IndexSet`]: ../struct.IndexSet.html -/// [`par_difference`]: ../struct.IndexSet.html#method.par_difference pub struct ParDifference<'a, T, S1, S2> { set1: &'a IndexSet, set2: &'a IndexSet, @@ -345,13 +337,10 @@ where } } -/// A parallel iterator producing elements in the intersection of `IndexSet`s. +/// A parallel iterator producing elements in the intersection of [`IndexSet`]s. /// -/// This `struct` is created by the [`par_intersection`] method on [`IndexSet`]. +/// This `struct` is created by the [`IndexSet::par_intersection`] method. /// See its documentation for more. -/// -/// [`IndexSet`]: ../struct.IndexSet.html -/// [`par_intersection`]: ../struct.IndexSet.html#method.par_intersection pub struct ParIntersection<'a, T, S1, S2> { set1: &'a IndexSet, set2: &'a IndexSet, @@ -396,13 +385,10 @@ where } } -/// A parallel iterator producing elements in the symmetric difference of `IndexSet`s. +/// A parallel iterator producing elements in the symmetric difference of [`IndexSet`]s. /// -/// This `struct` is created by the [`par_symmetric_difference`] method on -/// [`IndexSet`]. See its documentation for more. -/// -/// [`IndexSet`]: ../struct.IndexSet.html -/// [`par_symmetric_difference`]: ../struct.IndexSet.html#method.par_symmetric_difference +/// This `struct` is created by the [`IndexSet::par_symmetric_difference`] method. +/// See its documentation for more. pub struct ParSymmetricDifference<'a, T, S1, S2> { set1: &'a IndexSet, set2: &'a IndexSet, @@ -447,13 +433,10 @@ where } } -/// A parallel iterator producing elements in the union of `IndexSet`s. +/// A parallel iterator producing elements in the union of [`IndexSet`]s. /// -/// This `struct` is created by the [`par_union`] method on [`IndexSet`]. +/// This `struct` is created by the [`IndexSet::par_union`] method. /// See its documentation for more. -/// -/// [`IndexSet`]: ../struct.IndexSet.html -/// [`par_union`]: ../struct.IndexSet.html#method.par_union pub struct ParUnion<'a, T, S1, S2> { set1: &'a IndexSet, set2: &'a IndexSet, diff --git a/src/serde.rs b/src/serde.rs index 37de3e3e..6e55d206 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -1,3 +1,5 @@ +#![cfg_attr(docsrs, doc(cfg(feature = "serde")))] + use serde::de::value::{MapDeserializer, SeqDeserializer}; use serde::de::{ Deserialize, Deserializer, Error, IntoDeserializer, MapAccess, SeqAccess, Visitor, diff --git a/src/set.rs b/src/set.rs index fea1edb7..4859ea74 100644 --- a/src/set.rs +++ b/src/set.rs @@ -1,4 +1,4 @@ -//! A hash set implemented using `IndexMap` +//! A hash set implemented using [`IndexMap`] mod iter; mod slice; @@ -31,8 +31,9 @@ type Bucket = super::Bucket; /// A hash set where the iteration order of the values is independent of their /// hash values. /// -/// The interface is closely compatible with the standard `HashSet`, but also -/// has additional features. +/// The interface is closely compatible with the standard +/// [`HashSet`][std::collections::HashSet], +/// but also has additional features. /// /// # Order /// @@ -43,11 +44,12 @@ type Bucket = super::Bucket; /// already present. /// /// All iterators traverse the set *in order*. Set operation iterators like -/// `union` produce a concatenated order, as do their matching "bitwise" +/// [`IndexSet::union`] produce a concatenated order, as do their matching "bitwise" /// operators. See their documentation for specifics. /// /// The insertion order is preserved, with **notable exceptions** like the -/// `.remove()` or `.swap_remove()` methods. Methods such as `.sort_by()` of +/// [`.remove()`][Self::remove] or [`.swap_remove()`][Self::swap_remove] methods. +/// Methods such as [`.sort_by()`][Self::sort_by] of /// course result in a new order, depending on the sorting order. /// /// # Indices @@ -232,7 +234,7 @@ impl IndexSet { /// Clears the `IndexSet` in the given index range, returning those values /// as a drain iterator. /// - /// The range may be any type that implements `RangeBounds`, + /// The range may be any type that implements [`RangeBounds`], /// including all of the `std::ops::Range*` types, or even a tuple pair of /// `Bound` start and end values. To drain the set entirely, use `RangeFull` /// like `set.drain(..)`. @@ -478,7 +480,7 @@ where /// Remove the value from the set, and return `true` if it was present. /// - /// Like `Vec::swap_remove`, the value is removed by swapping it with the + /// Like [`Vec::swap_remove`], the value is removed by swapping it with the /// last element of the set and popping it off. **This perturbs /// the position of what used to be the last element!** /// @@ -494,7 +496,7 @@ where /// Remove the value from the set, and return `true` if it was present. /// - /// Like `Vec::remove`, the value is removed by shifting all of the + /// Like [`Vec::remove`], the value is removed by shifting all of the /// elements that follow it, preserving their relative order. /// **This perturbs the index of all of those elements!** /// @@ -527,7 +529,7 @@ where /// Removes and returns the value in the set, if any, that is equal to the /// given one. /// - /// Like `Vec::swap_remove`, the value is removed by swapping it with the + /// Like [`Vec::swap_remove`], the value is removed by swapping it with the /// last element of the set and popping it off. **This perturbs /// the position of what used to be the last element!** /// @@ -544,7 +546,7 @@ where /// Removes and returns the value in the set, if any, that is equal to the /// given one. /// - /// Like `Vec::remove`, the value is removed by shifting all of the + /// Like [`Vec::remove`], the value is removed by shifting all of the /// elements that follow it, preserving their relative order. /// **This perturbs the index of all of those elements!** /// @@ -560,7 +562,7 @@ where /// Remove the value from the set return it and the index it had. /// - /// Like `Vec::swap_remove`, the value is removed by swapping it with the + /// Like [`Vec::swap_remove`], the value is removed by swapping it with the /// last element of the set and popping it off. **This perturbs /// the position of what used to be the last element!** /// @@ -574,7 +576,7 @@ where /// Remove the value from the set return it and the index it had. /// - /// Like `Vec::remove`, the value is removed by shifting all of the + /// Like [`Vec::remove`], the value is removed by shifting all of the /// elements that follow it, preserving their relative order. /// **This perturbs the index of all of those elements!** /// @@ -809,7 +811,7 @@ impl IndexSet { /// /// Valid indices are *0 <= index < self.len()* /// - /// Like `Vec::swap_remove`, the value is removed by swapping it with the + /// Like [`Vec::swap_remove`], the value is removed by swapping it with the /// last element of the set and popping it off. **This perturbs /// the position of what used to be the last element!** /// @@ -822,7 +824,7 @@ impl IndexSet { /// /// Valid indices are *0 <= index < self.len()* /// - /// Like `Vec::remove`, the value is removed by shifting all of the + /// Like [`Vec::remove`], the value is removed by shifting all of the /// elements that follow it, preserving their relative order. /// **This perturbs the index of all of those elements!** /// @@ -852,7 +854,7 @@ impl IndexSet { } } -/// Access `IndexSet` values at indexed positions. +/// Access [`IndexSet`] values at indexed positions. /// /// # Examples /// @@ -951,7 +953,7 @@ impl Default for IndexSet where S: Default, { - /// Return an empty `IndexSet` + /// Return an empty [`IndexSet`] fn default() -> Self { IndexSet { map: IndexMap::default(), diff --git a/src/set/iter.rs b/src/set/iter.rs index 828756da..aeef07ea 100644 --- a/src/set/iter.rs +++ b/src/set/iter.rs @@ -24,13 +24,10 @@ impl IntoIterator for IndexSet { } } -/// An iterator over the items of a `IndexSet`. +/// An iterator over the items of an [`IndexSet`]. /// -/// This `struct` is created by the [`iter`] method on [`IndexSet`]. +/// This `struct` is created by the [`IndexSet::iter`] method. /// See its documentation for more. -/// -/// [`IndexSet`]: struct.IndexSet.html -/// [`iter`]: struct.IndexSet.html#method.iter pub struct Iter<'a, T> { iter: SliceIter<'a, Bucket>, } @@ -86,13 +83,10 @@ impl Default for Iter<'_, T> { } } -/// An owning iterator over the items of a `IndexSet`. -/// -/// This `struct` is created by the [`into_iter`] method on [`IndexSet`] -/// (provided by the `IntoIterator` trait). See its documentation for more. +/// An owning iterator over the items of an [`IndexSet`]. /// -/// [`IndexSet`]: struct.IndexSet.html -/// [`into_iter`]: struct.IndexSet.html#method.into_iter +/// This `struct` is created by the [`IndexSet::into_iter`] method +/// (provided by the [`IntoIterator`] trait). See its documentation for more. pub struct IntoIter { iter: vec::IntoIter>, } @@ -143,13 +137,10 @@ impl Default for IntoIter { } } -/// A draining iterator over the items of a `IndexSet`. +/// A draining iterator over the items of an [`IndexSet`]. /// -/// This `struct` is created by the [`drain`] method on [`IndexSet`]. +/// This `struct` is created by the [`IndexSet::drain`] method. /// See its documentation for more. -/// -/// [`IndexSet`]: struct.IndexSet.html -/// [`drain`]: struct.IndexSet.html#method.drain pub struct Drain<'a, T> { iter: vec::Drain<'a, Bucket>, } @@ -190,13 +181,10 @@ impl fmt::Debug for Drain<'_, T> { } } -/// A lazy iterator producing elements in the difference of `IndexSet`s. +/// A lazy iterator producing elements in the difference of [`IndexSet`]s. /// -/// This `struct` is created by the [`difference`] method on [`IndexSet`]. +/// This `struct` is created by the [`IndexSet::difference`] method. /// See its documentation for more. -/// -/// [`IndexSet`]: struct.IndexSet.html -/// [`difference`]: struct.IndexSet.html#method.difference pub struct Difference<'a, T, S> { iter: Iter<'a, T>, other: &'a IndexSet, @@ -273,13 +261,10 @@ where } } -/// A lazy iterator producing elements in the intersection of `IndexSet`s. +/// A lazy iterator producing elements in the intersection of [`IndexSet`]s. /// -/// This `struct` is created by the [`intersection`] method on [`IndexSet`]. +/// This `struct` is created by the [`IndexSet::intersection`] method. /// See its documentation for more. -/// -/// [`IndexSet`]: struct.IndexSet.html -/// [`intersection`]: struct.IndexSet.html#method.intersection pub struct Intersection<'a, T, S> { iter: Iter<'a, T>, other: &'a IndexSet, @@ -356,13 +341,10 @@ where } } -/// A lazy iterator producing elements in the symmetric difference of `IndexSet`s. -/// -/// This `struct` is created by the [`symmetric_difference`] method on -/// [`IndexSet`]. See its documentation for more. +/// A lazy iterator producing elements in the symmetric difference of [`IndexSet`]s. /// -/// [`IndexSet`]: struct.IndexSet.html -/// [`symmetric_difference`]: struct.IndexSet.html#method.symmetric_difference +/// This `struct` is created by the [`IndexSet::symmetric_difference`] method. +/// See its documentation for more. pub struct SymmetricDifference<'a, T, S1, S2> { iter: Chain, Difference<'a, T, S1>>, } @@ -451,13 +433,10 @@ where } } -/// A lazy iterator producing elements in the union of `IndexSet`s. +/// A lazy iterator producing elements in the union of [`IndexSet`]s. /// -/// This `struct` is created by the [`union`] method on [`IndexSet`]. +/// This `struct` is created by the [`IndexSet::union`] method. /// See its documentation for more. -/// -/// [`IndexSet`]: struct.IndexSet.html -/// [`union`]: struct.IndexSet.html#method.union pub struct Union<'a, T, S> { iter: Chain, Difference<'a, T, S>>, } diff --git a/src/set/slice.rs b/src/set/slice.rs index 251d0677..be006c97 100644 --- a/src/set/slice.rs +++ b/src/set/slice.rs @@ -8,13 +8,13 @@ use core::fmt; use core::hash::{Hash, Hasher}; use core::ops::{self, Bound, Index, RangeBounds}; -/// A dynamically-sized slice of values in an `IndexSet`. +/// A dynamically-sized slice of values in an [`IndexSet`]. /// /// This supports indexed operations much like a `[T]` slice, /// but not any hashed operations on the values. /// -/// Unlike `IndexSet`, `Slice` does consider the order for `PartialEq` -/// and `Eq`, and it also implements `PartialOrd`, `Ord`, and `Hash`. +/// Unlike `IndexSet`, `Slice` does consider the order for [`PartialEq`] +/// and [`Eq`], and it also implements [`PartialOrd`], [`Ord`], and [`Hash`]. #[repr(transparent)] pub struct Slice { pub(crate) entries: [Bucket], From 908084ec71388f0312e4fba81e25d272d691732b Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Mon, 15 Jan 2024 17:38:42 -0800 Subject: [PATCH 148/236] Relax some over-constrained impl bounds Most of these are removing `K: Hash + Eq` and/or `S: BuildHasher` where we don't actually need to hash anything. In some cases, we only get away with that because we have a saved hash in each entry `Bucket`. I *suspect* we still kept the constraints as API protection, in case we ever wanted to change our mind and stop saving the hash. However, there are a few other unconstrained methods that also need the saved hash, like `swap_remove_index` going back long before version 1.0 of the crate, so that ship has sailed. --- src/map.rs | 35 +++++++++++---------- src/map/serde_seq.rs | 3 +- src/mutable_keys.rs | 1 - src/rayon/map.rs | 3 +- src/rayon/set.rs | 3 +- src/serde.rs | 6 ++-- src/set.rs | 75 +++++++++++++++++++++++--------------------- 7 files changed, 64 insertions(+), 62 deletions(-) diff --git a/src/map.rs b/src/map.rs index c7e2f4d5..0e73ec8f 100644 --- a/src/map.rs +++ b/src/map.rs @@ -316,13 +316,7 @@ impl IndexMap { hash_builder: self.hash_builder.clone(), } } -} -impl IndexMap -where - K: Hash + Eq, - S: BuildHasher, -{ /// Reserve capacity for `additional` more key-value pairs. /// /// Computes in **O(n)** time. @@ -374,13 +368,13 @@ where pub fn shrink_to(&mut self, min_capacity: usize) { self.core.shrink_to(min_capacity); } +} - fn hash(&self, key: &Q) -> HashValue { - let mut h = self.hash_builder.build_hasher(); - key.hash(&mut h); - HashValue(h.finish() as usize) - } - +impl IndexMap +where + K: Hash + Eq, + S: BuildHasher, +{ /// Insert a key-value pair in the map. /// /// If an equivalent key already exists in the map: the key remains and @@ -424,6 +418,17 @@ where let hash = self.hash(&key); self.core.entry(hash, key) } +} + +impl IndexMap +where + S: BuildHasher, +{ + fn hash(&self, key: &Q) -> HashValue { + let mut h = self.hash_builder.build_hasher(); + key.hash(&mut h); + HashValue(h.finish() as usize) + } /// Return `true` if an equivalent to `key` exists in the map. /// @@ -663,7 +668,9 @@ where let hash = self.hash(key); self.core.shift_remove_full(hash, key) } +} +impl IndexMap { /// Remove the last key-value pair /// /// This preserves the order of the remaining elements. @@ -861,9 +868,7 @@ where pub fn reverse(&mut self) { self.core.reverse() } -} -impl IndexMap { /// Returns a slice of all the key-value pairs in the map. /// /// Computes in **O(1)** time. @@ -1037,7 +1042,6 @@ impl IndexMap { impl Index<&Q> for IndexMap where Q: Hash + Equivalent, - K: Hash + Eq, S: BuildHasher, { type Output = V; @@ -1082,7 +1086,6 @@ where impl IndexMut<&Q> for IndexMap where Q: Hash + Equivalent, - K: Hash + Eq, S: BuildHasher, { /// Returns a mutable reference to the value corresponding to the supplied `key`. diff --git a/src/map/serde_seq.rs b/src/map/serde_seq.rs index 4282cb6e..c35a234d 100644 --- a/src/map/serde_seq.rs +++ b/src/map/serde_seq.rs @@ -75,9 +75,8 @@ where /// ``` pub fn serialize(map: &IndexMap, serializer: T) -> Result where - K: Serialize + Hash + Eq, + K: Serialize, V: Serialize, - S: BuildHasher, T: Serializer, { serializer.collect_seq(map) diff --git a/src/mutable_keys.rs b/src/mutable_keys.rs index 478622c6..917af8c6 100644 --- a/src/mutable_keys.rs +++ b/src/mutable_keys.rs @@ -54,7 +54,6 @@ pub trait MutableKeys: private::Sealed { /// See [`MutableKeys`] for more information. impl MutableKeys for IndexMap where - K: Eq + Hash, S: BuildHasher, { type Key = K; diff --git a/src/rayon/map.rs b/src/rayon/map.rs index 4613e7bd..8236cf70 100644 --- a/src/rayon/map.rs +++ b/src/rayon/map.rs @@ -408,9 +408,8 @@ where impl IndexMap where - K: Hash + Eq + Send, + K: Send, V: Send, - S: BuildHasher, { /// Sort the map’s key-value pairs in parallel, by the default ordering of the keys. pub fn par_sort_keys(&mut self) diff --git a/src/rayon/set.rs b/src/rayon/set.rs index db6a5f03..3904234b 100644 --- a/src/rayon/set.rs +++ b/src/rayon/set.rs @@ -484,8 +484,7 @@ where /// The following methods **require crate feature `"rayon"`**. impl IndexSet where - T: Hash + Eq + Send, - S: BuildHasher + Send, + T: Send, { /// Sort the set’s values in parallel by their default ordering. pub fn par_sort(&mut self) diff --git a/src/serde.rs b/src/serde.rs index 6e55d206..8920a0bd 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -14,9 +14,8 @@ use crate::IndexMap; impl Serialize for IndexMap where - K: Serialize + Hash + Eq, + K: Serialize, V: Serialize, - S: BuildHasher, { fn serialize(&self, serializer: T) -> Result where @@ -87,8 +86,7 @@ use crate::IndexSet; impl Serialize for IndexSet where - T: Serialize + Hash + Eq, - S: BuildHasher, + T: Serialize, { fn serialize(&self, serializer: Se) -> Result where diff --git a/src/set.rs b/src/set.rs index 4859ea74..f691f035 100644 --- a/src/set.rs +++ b/src/set.rs @@ -266,13 +266,7 @@ impl IndexSet { map: self.map.split_off(at), } } -} -impl IndexSet -where - T: Hash + Eq, - S: BuildHasher, -{ /// Reserve capacity for `additional` more values. /// /// Computes in **O(n)** time. @@ -324,7 +318,13 @@ where pub fn shrink_to(&mut self, min_capacity: usize) { self.map.shrink_to(min_capacity); } +} +impl IndexSet +where + T: Hash + Eq, + S: BuildHasher, +{ /// Insert the value into the set. /// /// If an equivalent item already exists in the set, it returns @@ -351,6 +351,33 @@ where (index, existing.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. + /// + /// Computes in **O(1)** time (average). + pub fn replace(&mut self, value: T) -> Option { + self.replace_full(value).1 + } + + /// 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 index of the item and its replaced value. + /// + /// Computes in **O(1)** time (average). + pub fn replace_full(&mut self, value: T) -> (usize, Option) { + use super::map::Entry::*; + + match self.map.entry(value) { + Vacant(e) => { + let index = e.index(); + e.insert(()); + (index, None) + } + Occupied(e) => (e.index(), Some(e.replace_key())), + } + } + /// Return an iterator over the values that are in `self` but not `other`. /// /// Values are produced in the same order that they appear in `self`. @@ -396,7 +423,12 @@ where { Union::new(self, other) } +} +impl IndexSet +where + S: BuildHasher, +{ /// Return `true` if an equivalent to `value` exists in the set. /// /// Computes in **O(1)** time (average). @@ -436,33 +468,6 @@ where self.map.get_index_of(value) } - /// 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. - /// - /// Computes in **O(1)** time (average). - pub fn replace(&mut self, value: T) -> Option { - self.replace_full(value).1 - } - - /// 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 index of the item and its replaced value. - /// - /// Computes in **O(1)** time (average). - pub fn replace_full(&mut self, value: T) -> (usize, Option) { - use super::map::Entry::*; - - match self.map.entry(value) { - Vacant(e) => { - let index = e.index(); - e.insert(()); - (index, None) - } - Occupied(e) => (e.index(), Some(e.replace_key())), - } - } - /// Remove the value from the set, and return `true` if it was present. /// /// **NOTE:** This is equivalent to [`.swap_remove(value)`][Self::swap_remove], replacing this @@ -587,7 +592,9 @@ where { self.map.shift_remove_full(value).map(|(i, x, ())| (i, x)) } +} +impl IndexSet { /// Remove the last value /// /// This preserves the order of the remaining elements. @@ -756,9 +763,7 @@ where pub fn reverse(&mut self) { self.map.reverse() } -} -impl IndexSet { /// Returns a slice of all the values in the set. /// /// Computes in **O(1)** time. From 4a00bd36c6d77e143499ef2e67c90bac6d75b201 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 16 Jan 2024 16:39:37 -0800 Subject: [PATCH 149/236] Update all links to the indexmap-rs org --- Cargo.toml | 2 +- README.md | 4 ++-- RELEASES.md | 48 ++++++++++++++++++++++++------------------------ 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dd38adee..284de4a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "indexmap" edition = "2021" version = "2.1.0" documentation = "https://docs.rs/indexmap/" -repository = "https://github.com/bluss/indexmap" +repository = "https://github.com/indexmap-rs/indexmap" license = "Apache-2.0 OR MIT" description = "A hash table with consistent order and fast iteration." keywords = ["hashmap", "no_std"] diff --git a/README.md b/README.md index 1363cfdb..9112d528 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # indexmap -[![build status](https://github.com/bluss/indexmap/workflows/Continuous%20integration/badge.svg?branch=master)](https://github.com/bluss/indexmap/actions) +[![build status](https://github.com/indexmap-rs/indexmap/workflows/Continuous%20integration/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) @@ -54,4 +54,4 @@ which is roughly: # Recent Changes -See [RELEASES.md](https://github.com/bluss/indexmap/blob/master/RELEASES.md). +See [RELEASES.md](https://github.com/indexmap-rs/indexmap/blob/master/RELEASES.md). diff --git a/RELEASES.md b/RELEASES.md index 817a2dc2..6d9cf14a 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -95,7 +95,7 @@ - The new `IndexSet::replace_full` will return the index of the item along with the replaced value, if any, by @zakcutner in PR [222]. -[222]: https://github.com/bluss/indexmap/pull/222 +[222]: https://github.com/indexmap-rs/indexmap/pull/222 - 1.8.0 @@ -120,12 +120,12 @@ which sort in-place without preserving the order of equal items, by @bhgomes in PR [211]. -[195]: https://github.com/bluss/indexmap/pull/195 -[196]: https://github.com/bluss/indexmap/pull/196 -[197]: https://github.com/bluss/indexmap/pull/197 -[203]: https://github.com/bluss/indexmap/pull/203 -[205]: https://github.com/bluss/indexmap/pull/205 -[211]: https://github.com/bluss/indexmap/pull/211 +[195]: https://github.com/indexmap-rs/indexmap/pull/195 +[196]: https://github.com/indexmap-rs/indexmap/pull/196 +[197]: https://github.com/indexmap-rs/indexmap/pull/197 +[203]: https://github.com/indexmap-rs/indexmap/pull/203 +[205]: https://github.com/indexmap-rs/indexmap/pull/205 +[211]: https://github.com/indexmap-rs/indexmap/pull/211 - 1.7.0 @@ -141,8 +141,8 @@ - The new `Entry::or_insert_with_key` matches Rust 1.50's `Entry` method, passing `&K` to the callback to create a value, by @cuviper in PR [175]. -[170]: https://github.com/bluss/indexmap/pull/170 -[175]: https://github.com/bluss/indexmap/pull/175 +[170]: https://github.com/indexmap-rs/indexmap/pull/170 +[175]: https://github.com/indexmap-rs/indexmap/pull/175 - 1.6.1 @@ -153,8 +153,8 @@ `truncate`, `split_off`, `first`, `first_mut`, `last`, `last_mut`, and `swap_indices`, by @cuviper in PR [160]. -[158]: https://github.com/bluss/indexmap/pull/158 -[160]: https://github.com/bluss/indexmap/pull/160 +[158]: https://github.com/indexmap-rs/indexmap/pull/158 +[160]: https://github.com/indexmap-rs/indexmap/pull/160 - 1.6.0 @@ -168,7 +168,7 @@ want the default `S = RandomState`, bypassing the autodetection added in 1.3.0, by @cuviper in PR [145]. -[145]: https://github.com/bluss/indexmap/pull/145 +[145]: https://github.com/indexmap-rs/indexmap/pull/145 - 1.5.1 @@ -178,9 +178,9 @@ - `drain` now accepts any `R: RangeBounds` by @cuviper in PR [142]. -[132]: https://github.com/bluss/indexmap/pull/132 -[141]: https://github.com/bluss/indexmap/pull/141 -[142]: https://github.com/bluss/indexmap/pull/142 +[132]: https://github.com/indexmap-rs/indexmap/pull/132 +[141]: https://github.com/indexmap-rs/indexmap/pull/141 +[142]: https://github.com/indexmap-rs/indexmap/pull/142 - 1.5.0 @@ -196,10 +196,10 @@ - Add new method `reverse` by @linclelinkpart5 in PR [128] -[125]: https://github.com/bluss/indexmap/pull/125 -[128]: https://github.com/bluss/indexmap/pull/128 -[131]: https://github.com/bluss/indexmap/pull/131 -[136]: https://github.com/bluss/indexmap/pull/136 +[125]: https://github.com/indexmap-rs/indexmap/pull/125 +[128]: https://github.com/indexmap-rs/indexmap/pull/128 +[131]: https://github.com/indexmap-rs/indexmap/pull/131 +[136]: https://github.com/indexmap-rs/indexmap/pull/136 - 1.4.0 @@ -212,9 +212,9 @@ now run using Rust 1.32 or later (MSRV for building the crate has not changed). by @kjeremy and @bluss -[123]: https://github.com/bluss/indexmap/issues/123 -[115]: https://github.com/bluss/indexmap/pull/115 -[120]: https://github.com/bluss/indexmap/pull/120 +[123]: https://github.com/indexmap-rs/indexmap/issues/123 +[115]: https://github.com/indexmap-rs/indexmap/pull/115 +[120]: https://github.com/indexmap-rs/indexmap/pull/120 - 1.3.2 @@ -358,7 +358,7 @@ See [#10] for more information. - Implement `Extend<(&K, &V)>` by @xfix. -[#10]: https://github.com/bluss/ordermap/pull/10 +[#10]: https://github.com/indexmap-rs/indexmap/pull/10 - 0.2.13 @@ -407,7 +407,7 @@ - Improved performance of `.insert()` ([#3]) by @pczarn. -[#3]: https://github.com/bluss/ordermap/pull/3 +[#3]: https://github.com/indexmap-rs/indexmap/pull/3 - 0.2.3 From ec86349338d384a2afa0e492d36f7422325e15f7 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 16 Jan 2024 17:08:25 -0800 Subject: [PATCH 150/236] CI: Add a "Complete" job for branch protection --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d8203430..3eebe4db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,7 @@ on: branches: [ master ] pull_request: branches: [ master ] + merge_group: name: Continuous integration @@ -110,3 +111,10 @@ jobs: - run: cargo +nightly hack generate-lockfile --remove-dev-deps -Z direct-minimal-versions - name: Build run: cargo build --verbose --all-features + + done: + name: Complete + runs-on: ubuntu-latest + needs: [tests, nostd_build, clippy, miri, minimal-versions] + steps: + - run: exit 0 From 33155c7e07dafb136184d02db545a18c51f1be7f Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 12 Jan 2024 15:52:47 -0800 Subject: [PATCH 151/236] Add splicing iterators `IndexMap::splice` and `IndexSet::splice` will drain the specified range, and then insert items from an iterator to replace that range. This is like `Vec::splice`, except if any new keys match the retained entries outside the range, then those keep their relative position. --- src/map.rs | 40 ++++++++++++++- src/map/core.rs | 26 +++++++++- src/map/iter.rs | 133 +++++++++++++++++++++++++++++++++++++++++++++++- src/set.rs | 41 ++++++++++++++- src/set/iter.rs | 104 +++++++++++++++++++++++++++++++++++++ 5 files changed, 339 insertions(+), 5 deletions(-) diff --git a/src/map.rs b/src/map.rs index 0e73ec8f..e46846cd 100644 --- a/src/map.rs +++ b/src/map.rs @@ -14,7 +14,7 @@ mod tests; pub use self::core::{Entry, IndexedEntry, OccupiedEntry, VacantEntry}; pub use self::iter::{ - Drain, IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Values, ValuesMut, + Drain, IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Splice, Values, ValuesMut, }; pub use self::slice::Slice; pub use crate::mutable_keys::MutableKeys; @@ -418,6 +418,44 @@ where let hash = self.hash(&key); self.core.entry(hash, key) } + + /// Creates a splicing iterator that replaces the specified range in the map + /// with the given `replace_with` key-value iterator and yields the removed + /// items. `replace_with` does not need to be the same length as `range`. + /// + /// The `range` is removed even if the iterator is not consumed until the + /// end. It is unspecified how many elements are removed from the map if the + /// `Splice` value is leaked. + /// + /// The input iterator `replace_with` is only consumed when the `Splice` + /// value is dropped. If a key from the iterator matches an existing entry + /// in the map (outside of `range`), then the value will be updated in that + /// position. Otherwise, the new key-value pair will be inserted in the + /// replaced `range`. + /// + /// ***Panics*** if the starting point is greater than the end point or if + /// the end point is greater than the length of the map. + /// + /// # Examples + /// + /// ``` + /// use indexmap::IndexMap; + /// + /// let mut map = IndexMap::from([(0, '_'), (1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]); + /// let new = [(5, 'E'), (4, 'D'), (3, 'C'), (2, 'B'), (1, 'A')]; + /// let removed: Vec<_> = map.splice(2..4, new).collect(); + /// + /// // 1 and 4 got new values, while 5, 3, and 2 were newly inserted. + /// assert!(map.into_iter().eq([(0, '_'), (1, 'A'), (5, 'E'), (3, 'C'), (2, 'B'), (4, 'D')])); + /// assert_eq!(removed, &[(2, 'b'), (3, 'c')]); + /// ``` + pub fn splice(&mut self, range: R, replace_with: I) -> Splice<'_, I::IntoIter, K, V, S> + where + R: RangeBounds, + I: IntoIterator, + { + Splice::new(self, range, replace_with.into_iter()) + } } impl IndexMap diff --git a/src/map/core.rs b/src/map/core.rs index e6e70e50..370ed13a 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -11,7 +11,7 @@ mod raw; use hashbrown::raw::RawTable; -use crate::vec::{Drain, Vec}; +use crate::vec::{self, Vec}; use crate::TryReserveError; use core::fmt; use core::mem; @@ -160,7 +160,7 @@ impl IndexMapCore { } } - pub(crate) fn drain(&mut self, range: R) -> Drain<'_, Bucket> + pub(crate) fn drain(&mut self, range: R) -> vec::Drain<'_, Bucket> where R: RangeBounds, { @@ -192,6 +192,28 @@ impl IndexMapCore { Self { indices, entries } } + pub(crate) fn split_splice(&mut self, range: R) -> (Self, vec::IntoIter>) + where + R: RangeBounds, + { + let range = simplify_range(range, self.len()); + self.erase_indices(range.start, self.entries.len()); + let entries = self.entries.split_off(range.end); + let drained = self.entries.split_off(range.start); + + let mut indices = RawTable::with_capacity(entries.len()); + raw::insert_bulk_no_grow(&mut indices, &entries); + (Self { indices, entries }, drained.into_iter()) + } + + /// Append from another map without checking whether items already exist. + pub(crate) fn append_unchecked(&mut self, other: &mut Self) { + self.reserve(other.len()); + raw::insert_bulk_no_grow(&mut self.indices, &other.entries); + self.entries.append(&mut other.entries); + other.indices.clear(); + } + /// Reserve capacity for `additional` more key-value pairs. pub(crate) fn reserve(&mut self, additional: usize) { self.indices.reserve(additional, get_hash(&self.entries)); diff --git a/src/map/iter.rs b/src/map/iter.rs index 2039de9c..1ec3703c 100644 --- a/src/map/iter.rs +++ b/src/map/iter.rs @@ -1,9 +1,11 @@ +use super::core::IndexMapCore; use super::{Bucket, Entries, IndexMap, Slice}; use alloc::vec::{self, Vec}; use core::fmt; +use core::hash::{BuildHasher, Hash}; use core::iter::FusedIterator; -use core::ops::Index; +use core::ops::{Index, RangeBounds}; use core::slice; impl<'a, K, V, S> IntoIterator for &'a IndexMap { @@ -580,3 +582,132 @@ impl Default for IntoValues { } } } + +/// A splicing iterator for `IndexMap`. +/// +/// This `struct` is created by [`IndexMap::splice()`]. +/// See its documentation for more. +pub struct Splice<'a, I, K, V, S> +where + I: Iterator, + K: Hash + Eq, + S: BuildHasher, +{ + map: &'a mut IndexMap, + tail: IndexMapCore, + drain: vec::IntoIter>, + replace_with: I, +} + +impl<'a, I, K, V, S> Splice<'a, I, K, V, S> +where + I: Iterator, + K: Hash + Eq, + S: BuildHasher, +{ + pub(super) fn new(map: &'a mut IndexMap, range: R, replace_with: I) -> Self + where + R: RangeBounds, + { + let (tail, drain) = map.core.split_splice(range); + Self { + map, + tail, + drain, + replace_with, + } + } +} + +impl Drop for Splice<'_, I, K, V, S> +where + I: Iterator, + K: Hash + Eq, + S: BuildHasher, +{ + fn drop(&mut self) { + // Finish draining unconsumed items. We don't strictly *have* to do this + // manually, since we already split it into separate memory, but it will + // match the drop order of `vec::Splice` items this way. + let _ = self.drain.nth(usize::MAX); + + // Now insert all the new items. If a key matches an existing entry, it + // keeps the original position and only replaces the value, like `insert`. + while let Some((key, value)) = self.replace_with.next() { + // Since the tail is disjoint, we can try to update it first, + // or else insert (update or append) the primary map. + let hash = self.map.hash(&key); + if let Some(i) = self.tail.get_index_of(hash, &key) { + self.tail.as_entries_mut()[i].value = value; + } else { + self.map.core.insert_full(hash, key, value); + } + } + + // Finally, re-append the tail + self.map.core.append_unchecked(&mut self.tail); + } +} + +impl Iterator for Splice<'_, I, K, V, S> +where + I: Iterator, + K: Hash + Eq, + S: BuildHasher, +{ + type Item = (K, V); + + fn next(&mut self) -> Option { + self.drain.next().map(Bucket::key_value) + } + + fn size_hint(&self) -> (usize, Option) { + self.drain.size_hint() + } +} + +impl DoubleEndedIterator for Splice<'_, I, K, V, S> +where + I: Iterator, + K: Hash + Eq, + S: BuildHasher, +{ + fn next_back(&mut self) -> Option { + self.drain.next_back().map(Bucket::key_value) + } +} + +impl ExactSizeIterator for Splice<'_, I, K, V, S> +where + I: Iterator, + K: Hash + Eq, + S: BuildHasher, +{ + fn len(&self) -> usize { + self.drain.len() + } +} + +impl FusedIterator for Splice<'_, I, K, V, S> +where + I: Iterator, + K: Hash + Eq, + S: BuildHasher, +{ +} + +impl<'a, I, K, V, S> fmt::Debug for Splice<'a, I, K, V, S> +where + I: fmt::Debug + Iterator, + K: fmt::Debug + Hash + Eq, + V: fmt::Debug, + S: BuildHasher, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Follow `vec::Splice` in only printing the drain and replacement + f.debug_struct("Splice") + .field("drain", &self.drain) + .field("replace_with", &self.replace_with) + .finish() + } +} diff --git a/src/set.rs b/src/set.rs index f691f035..ec110b26 100644 --- a/src/set.rs +++ b/src/set.rs @@ -6,7 +6,9 @@ mod slice; #[cfg(test)] mod tests; -pub use self::iter::{Difference, Drain, Intersection, IntoIter, Iter, SymmetricDifference, Union}; +pub use self::iter::{ + Difference, Drain, Intersection, IntoIter, Iter, Splice, SymmetricDifference, Union, +}; pub use self::slice::Slice; #[cfg(feature = "rayon")] @@ -423,6 +425,43 @@ where { Union::new(self, other) } + + /// Creates a splicing iterator that replaces the specified range in the set + /// with the given `replace_with` iterator and yields the removed items. + /// `replace_with` does not need to be the same length as `range`. + /// + /// The `range` is removed even if the iterator is not consumed until the + /// end. It is unspecified how many elements are removed from the set if the + /// `Splice` value is leaked. + /// + /// The input iterator `replace_with` is only consumed when the `Splice` + /// value is dropped. If a value from the iterator matches an existing entry + /// in the set (outside of `range`), then the original will be unchanged. + /// Otherwise, the new value will be inserted in the replaced `range`. + /// + /// ***Panics*** if the starting point is greater than the end point or if + /// the end point is greater than the length of the set. + /// + /// # Examples + /// + /// ``` + /// use indexmap::IndexSet; + /// + /// let mut set = IndexSet::from([0, 1, 2, 3, 4]); + /// let new = [5, 4, 3, 2, 1]; + /// let removed: Vec<_> = set.splice(2..4, new).collect(); + /// + /// // 1 and 4 kept their positions, while 5, 3, and 2 were newly inserted. + /// assert!(set.into_iter().eq([0, 1, 5, 3, 2, 4])); + /// assert_eq!(removed, &[2, 3]); + /// ``` + pub fn splice(&mut self, range: R, replace_with: I) -> Splice<'_, I::IntoIter, T, S> + where + R: RangeBounds, + I: IntoIterator, + { + Splice::new(self, range, replace_with.into_iter()) + } } impl IndexSet diff --git a/src/set/iter.rs b/src/set/iter.rs index aeef07ea..3f8033c2 100644 --- a/src/set/iter.rs +++ b/src/set/iter.rs @@ -4,6 +4,7 @@ use alloc::vec::{self, Vec}; use core::fmt; use core::hash::{BuildHasher, Hash}; use core::iter::{Chain, FusedIterator}; +use core::ops::RangeBounds; use core::slice::Iter as SliceIter; impl<'a, T, S> IntoIterator for &'a IndexSet { @@ -520,3 +521,106 @@ where f.debug_list().entries(self.clone()).finish() } } + +/// A splicing iterator for `IndexSet`. +/// +/// This `struct` is created by [`IndexSet::splice()`]. +/// See its documentation for more. +pub struct Splice<'a, I, T, S> +where + I: Iterator, + T: Hash + Eq, + S: BuildHasher, +{ + iter: crate::map::Splice<'a, UnitValue, T, (), S>, +} + +impl<'a, I, T, S> Splice<'a, I, T, S> +where + I: Iterator, + T: Hash + Eq, + S: BuildHasher, +{ + pub(super) fn new(set: &'a mut IndexSet, range: R, replace_with: I) -> Self + where + R: RangeBounds, + { + Self { + iter: set.map.splice(range, UnitValue(replace_with)), + } + } +} + +impl Iterator for Splice<'_, I, T, S> +where + I: Iterator, + T: Hash + Eq, + S: BuildHasher, +{ + type Item = T; + + fn next(&mut self) -> Option { + Some(self.iter.next()?.0) + } + + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl DoubleEndedIterator for Splice<'_, I, T, S> +where + I: Iterator, + T: Hash + Eq, + S: BuildHasher, +{ + fn next_back(&mut self) -> Option { + Some(self.iter.next_back()?.0) + } +} + +impl ExactSizeIterator for Splice<'_, I, T, S> +where + I: Iterator, + T: Hash + Eq, + S: BuildHasher, +{ + fn len(&self) -> usize { + self.iter.len() + } +} + +impl FusedIterator for Splice<'_, I, T, S> +where + I: Iterator, + T: Hash + Eq, + S: BuildHasher, +{ +} + +struct UnitValue(I); + +impl Iterator for UnitValue { + type Item = (I::Item, ()); + + fn next(&mut self) -> Option { + self.0.next().map(|x| (x, ())) + } +} + +impl<'a, I, T, S> fmt::Debug for Splice<'a, I, T, S> +where + I: fmt::Debug + Iterator, + T: fmt::Debug + Hash + Eq, + S: BuildHasher, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.iter, f) + } +} + +impl fmt::Debug for UnitValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.0, f) + } +} From d79a2159a63895313e037528619776f24c2b957d Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 16 Jan 2024 15:25:09 -0800 Subject: [PATCH 152/236] Refactor `Entry` with more separation from the `raw` part --- src/map/core.rs | 349 +----------------------------------- src/map/core/entry.rs | 403 ++++++++++++++++++++++++++++++++++++++++++ src/map/core/raw.rs | 81 +++------ 3 files changed, 433 insertions(+), 400 deletions(-) create mode 100644 src/map/core/entry.rs diff --git a/src/map/core.rs b/src/map/core.rs index 370ed13a..66644775 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -7,6 +7,7 @@ //! //! However, we should probably not let this show in the public API or docs. +mod entry; mod raw; use hashbrown::raw::RawTable; @@ -20,6 +21,8 @@ use core::ops::RangeBounds; use crate::util::simplify_range; use crate::{Bucket, Entries, Equivalent, HashValue}; +pub use entry::{Entry, IndexedEntry, OccupiedEntry, VacantEntry}; + /// Core of the map that does not depend on S pub(crate) struct IndexMapCore { /// indices mapping from the entry hash to its index. @@ -567,352 +570,6 @@ impl IndexMapCore { } } -/// Entry for an existing key-value pair in an [`IndexMap`][crate::IndexMap] -/// or a vacant location to insert one. -pub enum Entry<'a, K, V> { - /// Existing slot with equivalent key. - Occupied(OccupiedEntry<'a, K, V>), - /// Vacant slot (no equivalent key in the map). - Vacant(VacantEntry<'a, K, V>), -} - -impl<'a, K, V> Entry<'a, K, V> { - /// Inserts the given default value in the entry if it is vacant and returns a mutable - /// reference to it. Otherwise a mutable reference to an already existent value is returned. - /// - /// Computes in **O(1)** time (amortized average). - pub fn or_insert(self, default: V) -> &'a mut V { - match self { - Entry::Occupied(entry) => entry.into_mut(), - Entry::Vacant(entry) => entry.insert(default), - } - } - - /// Inserts the result of the `call` function in the entry if it is vacant and returns a mutable - /// reference to it. Otherwise a mutable reference to an already existent value is returned. - /// - /// Computes in **O(1)** time (amortized average). - pub fn or_insert_with(self, call: F) -> &'a mut V - where - F: FnOnce() -> V, - { - match self { - Entry::Occupied(entry) => entry.into_mut(), - Entry::Vacant(entry) => entry.insert(call()), - } - } - - /// Inserts the result of the `call` function with a reference to the entry's key if it is - /// vacant, and returns a mutable reference to the new value. Otherwise a mutable reference to - /// an already existent value is returned. - /// - /// Computes in **O(1)** time (amortized average). - pub fn or_insert_with_key(self, call: F) -> &'a mut V - where - F: FnOnce(&K) -> V, - { - match self { - Entry::Occupied(entry) => entry.into_mut(), - Entry::Vacant(entry) => { - let value = call(&entry.key); - entry.insert(value) - } - } - } - - /// Gets a reference to the entry's key, either within the map if occupied, - /// or else the new key that was used to find the entry. - pub fn key(&self) -> &K { - match *self { - Entry::Occupied(ref entry) => entry.key(), - Entry::Vacant(ref entry) => entry.key(), - } - } - - /// Return the index where the key-value pair exists or will be inserted. - pub fn index(&self) -> usize { - match *self { - Entry::Occupied(ref entry) => entry.index(), - Entry::Vacant(ref entry) => entry.index(), - } - } - - /// Modifies the entry if it is occupied. - pub fn and_modify(self, f: F) -> Self - where - F: FnOnce(&mut V), - { - match self { - Entry::Occupied(mut o) => { - f(o.get_mut()); - Entry::Occupied(o) - } - x => x, - } - } - - /// Inserts a default-constructed value in the entry if it is vacant and returns a mutable - /// reference to it. Otherwise a mutable reference to an already existent value is returned. - /// - /// Computes in **O(1)** time (amortized average). - pub fn or_default(self) -> &'a mut V - where - V: Default, - { - match self { - Entry::Occupied(entry) => entry.into_mut(), - Entry::Vacant(entry) => entry.insert(V::default()), - } - } -} - -impl fmt::Debug for Entry<'_, K, V> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - Entry::Vacant(ref v) => f.debug_tuple(stringify!(Entry)).field(v).finish(), - Entry::Occupied(ref o) => f.debug_tuple(stringify!(Entry)).field(o).finish(), - } - } -} - -pub use self::raw::OccupiedEntry; - -// Extra methods that don't threaten the unsafe encapsulation. -impl OccupiedEntry<'_, K, V> { - /// Sets the value of the entry to `value`, and returns the entry's old value. - pub fn insert(&mut self, value: V) -> V { - mem::replace(self.get_mut(), value) - } - - /// Remove the key, value pair stored in the map for this entry, and return the value. - /// - /// **NOTE:** This is equivalent to [`.swap_remove()`][Self::swap_remove], replacing this - /// entry's position with the last element, and it is deprecated in favor of calling that - /// explicitly. If you need to preserve the relative order of the keys in the map, use - /// [`.shift_remove()`][Self::shift_remove] instead. - #[deprecated(note = "`remove` disrupts the map order -- \ - use `swap_remove` or `shift_remove` for explicit behavior.")] - pub fn remove(self) -> V { - self.swap_remove() - } - - /// Remove the key, value pair stored in the map for this entry, and return the value. - /// - /// Like [`Vec::swap_remove`], the pair is removed by swapping it with the - /// last element of the map and popping it off. **This perturbs - /// the position of what used to be the last element!** - /// - /// Computes in **O(1)** time (average). - pub fn swap_remove(self) -> V { - self.swap_remove_entry().1 - } - - /// Remove the key, value pair stored in the map for this entry, and return the value. - /// - /// Like [`Vec::remove`], the pair is removed by shifting all of the - /// elements that follow it, preserving their relative order. - /// **This perturbs the index of all of those elements!** - /// - /// Computes in **O(n)** time (average). - pub fn shift_remove(self) -> V { - self.shift_remove_entry().1 - } - - /// Remove and return the key, value pair stored in the map for this entry - /// - /// **NOTE:** This is equivalent to [`.swap_remove_entry()`][Self::swap_remove_entry], - /// replacing this entry's position with the last element, and it is deprecated in favor of - /// calling that explicitly. If you need to preserve the relative order of the keys in the map, - /// use [`.shift_remove_entry()`][Self::shift_remove_entry] instead. - #[deprecated(note = "`remove_entry` disrupts the map order -- \ - use `swap_remove_entry` or `shift_remove_entry` for explicit behavior.")] - pub fn remove_entry(self) -> (K, V) { - self.swap_remove_entry() - } - - /// Remove and return the key, value pair stored in the map for this entry - /// - /// Like [`Vec::swap_remove`], the pair is removed by swapping it with the - /// last element of the map and popping it off. **This perturbs - /// the position of what used to be the last element!** - /// - /// Computes in **O(1)** time (average). - pub fn swap_remove_entry(self) -> (K, V) { - let (map, index) = self.remove_index(); - map.swap_remove_finish(index) - } - - /// Remove and return the key, value pair stored in the map for this entry - /// - /// Like [`Vec::remove`], the pair is removed by shifting all of the - /// elements that follow it, preserving their relative order. - /// **This perturbs the index of all of those elements!** - /// - /// Computes in **O(n)** time (average). - pub fn shift_remove_entry(self) -> (K, V) { - let (map, index) = self.remove_index(); - map.shift_remove_finish(index) - } -} - -impl fmt::Debug for OccupiedEntry<'_, K, V> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct(stringify!(OccupiedEntry)) - .field("key", self.key()) - .field("value", self.get()) - .finish() - } -} - -/// A view into a vacant entry in an [`IndexMap`][crate::IndexMap]. -/// It is part of the [`Entry`] enum. -pub struct VacantEntry<'a, K, V> { - map: &'a mut IndexMapCore, - hash: HashValue, - key: K, -} - -impl<'a, K, V> VacantEntry<'a, K, V> { - /// Gets a reference to the key that was used to find the entry. - pub fn key(&self) -> &K { - &self.key - } - - /// Takes ownership of the key, leaving the entry vacant. - pub fn into_key(self) -> K { - self.key - } - - /// Return the index where the key-value pair will be inserted. - pub fn index(&self) -> usize { - self.map.indices.len() - } - - /// 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); - &mut map.entries[i].value - } -} - -impl fmt::Debug for VacantEntry<'_, K, V> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple(stringify!(VacantEntry)) - .field(self.key()) - .finish() - } -} - -/// A view into an occupied entry in an [`IndexMap`][crate::IndexMap] obtained by index. -/// -/// This `struct` is created from the [`get_index_entry`][crate::IndexMap::get_index_entry] method. -pub struct IndexedEntry<'a, K, V> { - map: &'a mut IndexMapCore, - // We have a mutable reference to the map, which keeps the index - // valid and pointing to the correct entry. - index: usize, -} - -impl<'a, K, V> IndexedEntry<'a, K, V> { - pub(crate) fn new(map: &'a mut IndexMapCore, index: usize) -> Self { - Self { map, index } - } - - /// Gets a reference to the entry's key in the map. - pub fn key(&self) -> &K { - &self.map.entries[self.index].key - } - - /// Gets a reference to the entry's value in the map. - pub fn get(&self) -> &V { - &self.map.entries[self.index].value - } - - /// Gets a mutable reference to the entry's value in the map. - /// - /// If you need a reference which may outlive the destruction of the - /// `IndexedEntry` value, see [`into_mut`][Self::into_mut]. - pub fn get_mut(&mut self) -> &mut V { - &mut self.map.entries[self.index].value - } - - /// Sets the value of the entry to `value`, and returns the entry's old value. - pub fn insert(&mut self, value: V) -> V { - mem::replace(self.get_mut(), value) - } - - /// Return the index of the key-value pair - #[inline] - pub fn index(&self) -> usize { - self.index - } - - /// Converts into a mutable reference to the entry's value in the map, - /// with a lifetime bound to the map itself. - pub fn into_mut(self) -> &'a mut V { - &mut self.map.entries[self.index].value - } - - /// Remove and return the key, value pair stored in the map for this entry - /// - /// Like [`Vec::swap_remove`], the pair is removed by swapping it with the - /// last element of the map and popping it off. **This perturbs - /// the position of what used to be the last element!** - /// - /// Computes in **O(1)** time (average). - pub fn swap_remove_entry(self) -> (K, V) { - self.map.swap_remove_index(self.index).unwrap() - } - - /// Remove and return the key, value pair stored in the map for this entry - /// - /// Like [`Vec::remove`], the pair is removed by shifting all of the - /// elements that follow it, preserving their relative order. - /// **This perturbs the index of all of those elements!** - /// - /// Computes in **O(n)** time (average). - pub fn shift_remove_entry(self) -> (K, V) { - self.map.shift_remove_index(self.index).unwrap() - } - - /// Remove the key, value pair stored in the map for this entry, and return the value. - /// - /// Like [`Vec::swap_remove`], the pair is removed by swapping it with the - /// last element of the map and popping it off. **This perturbs - /// the position of what used to be the last element!** - /// - /// Computes in **O(1)** time (average). - pub fn swap_remove(self) -> V { - self.swap_remove_entry().1 - } - - /// Remove the key, value pair stored in the map for this entry, and return the value. - /// - /// Like [`Vec::remove`], the pair is removed by shifting all of the - /// elements that follow it, preserving their relative order. - /// **This perturbs the index of all of those elements!** - /// - /// Computes in **O(n)** time (average). - pub fn shift_remove(self) -> V { - self.shift_remove_entry().1 - } -} - -impl fmt::Debug for IndexedEntry<'_, K, V> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct(stringify!(IndexedEntry)) - .field("index", &self.index) - .field("key", self.key()) - .field("value", self.get()) - .finish() - } -} - #[test] fn assert_send_sync() { fn assert_send_sync() {} diff --git a/src/map/core/entry.rs b/src/map/core/entry.rs new file mode 100644 index 00000000..f98825c1 --- /dev/null +++ b/src/map/core/entry.rs @@ -0,0 +1,403 @@ +use super::raw::RawTableEntry; +use super::{get_hash, IndexMapCore}; +use crate::HashValue; +use core::{fmt, mem}; + +impl IndexMapCore { + pub(crate) fn entry(&mut self, hash: HashValue, key: K) -> Entry<'_, K, V> + where + K: Eq, + { + match self.raw_entry(hash, |k| *k == key) { + Ok(raw) => Entry::Occupied(OccupiedEntry { raw, key }), + Err(map) => Entry::Vacant(VacantEntry { map, hash, key }), + } + } +} + +/// Entry for an existing key-value pair in an [`IndexMap`][crate::IndexMap] +/// or a vacant location to insert one. +pub enum Entry<'a, K, V> { + /// Existing slot with equivalent key. + Occupied(OccupiedEntry<'a, K, V>), + /// Vacant slot (no equivalent key in the map). + Vacant(VacantEntry<'a, K, V>), +} + +impl<'a, K, V> Entry<'a, K, V> { + /// Return the index where the key-value pair exists or will be inserted. + pub fn index(&self) -> usize { + match *self { + Entry::Occupied(ref entry) => entry.index(), + Entry::Vacant(ref entry) => entry.index(), + } + } + + /// Inserts the given default value in the entry if it is vacant and returns a mutable + /// reference to it. Otherwise a mutable reference to an already existent value is returned. + /// + /// Computes in **O(1)** time (amortized average). + pub fn or_insert(self, default: V) -> &'a mut V { + match self { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => entry.insert(default), + } + } + + /// Inserts the result of the `call` function in the entry if it is vacant and returns a mutable + /// reference to it. Otherwise a mutable reference to an already existent value is returned. + /// + /// Computes in **O(1)** time (amortized average). + pub fn or_insert_with(self, call: F) -> &'a mut V + where + F: FnOnce() -> V, + { + match self { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => entry.insert(call()), + } + } + + /// Inserts the result of the `call` function with a reference to the entry's key if it is + /// vacant, and returns a mutable reference to the new value. Otherwise a mutable reference to + /// an already existent value is returned. + /// + /// Computes in **O(1)** time (amortized average). + pub fn or_insert_with_key(self, call: F) -> &'a mut V + where + F: FnOnce(&K) -> V, + { + match self { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => { + let value = call(&entry.key); + entry.insert(value) + } + } + } + + /// Gets a reference to the entry's key, either within the map if occupied, + /// or else the new key that was used to find the entry. + pub fn key(&self) -> &K { + match *self { + Entry::Occupied(ref entry) => entry.key(), + Entry::Vacant(ref entry) => entry.key(), + } + } + + /// Modifies the entry if it is occupied. + pub fn and_modify(mut self, f: F) -> Self + where + F: FnOnce(&mut V), + { + if let Entry::Occupied(entry) = &mut self { + f(entry.get_mut()); + } + self + } + + /// Inserts a default-constructed value in the entry if it is vacant and returns a mutable + /// reference to it. Otherwise a mutable reference to an already existent value is returned. + /// + /// Computes in **O(1)** time (amortized average). + pub fn or_default(self) -> &'a mut V + where + V: Default, + { + match self { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => entry.insert(V::default()), + } + } +} + +impl fmt::Debug for Entry<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Entry::Vacant(ref v) => f.debug_tuple(stringify!(Entry)).field(v).finish(), + Entry::Occupied(ref o) => f.debug_tuple(stringify!(Entry)).field(o).finish(), + } + } +} + +/// A view into an occupied entry in an [`IndexMap`][crate::IndexMap]. +/// It is part of the [`Entry`] enum. +pub struct OccupiedEntry<'a, K, V> { + raw: RawTableEntry<'a, K, V>, + key: K, +} + +impl<'a, K, V> OccupiedEntry<'a, K, V> { + /// Return the index of the key-value pair + #[inline] + pub fn index(&self) -> usize { + self.raw.index() + } + + /// Gets a reference to the entry's key in the map. + /// + /// Note that this is not the key that was used to find the entry. There may be an observable + /// difference if the key type has any distinguishing features outside of `Hash` and `Eq`, like + /// extra fields or the memory address of an allocation. + pub fn key(&self) -> &K { + &self.raw.bucket().key + } + + /// Gets a reference to the entry's value in the map. + pub fn get(&self) -> &V { + &self.raw.bucket().value + } + + /// Gets a mutable reference to the entry's value in the map. + /// + /// If you need a reference which may outlive the destruction of the + /// [`Entry`] value, see [`into_mut`][Self::into_mut]. + pub fn get_mut(&mut self) -> &mut V { + &mut self.raw.bucket_mut().value + } + + /// Converts into a mutable reference to the entry's value in the map, + /// with a lifetime bound to the map itself. + pub fn into_mut(self) -> &'a mut V { + &mut self.raw.into_bucket().value + } + + /// Put the new key in the occupied entry's key slot + pub(crate) fn replace_key(mut self) -> K { + let old_key = &mut self.raw.bucket_mut().key; + mem::replace(old_key, self.key) + } + + /// Sets the value of the entry to `value`, and returns the entry's old value. + pub fn insert(&mut self, value: V) -> V { + mem::replace(self.get_mut(), value) + } + + /// Remove the key, value pair stored in the map for this entry, and return the value. + /// + /// **NOTE:** This is equivalent to [`.swap_remove()`][Self::swap_remove], replacing this + /// entry's position with the last element, and it is deprecated in favor of calling that + /// explicitly. If you need to preserve the relative order of the keys in the map, use + /// [`.shift_remove()`][Self::shift_remove] instead. + #[deprecated(note = "`remove` disrupts the map order -- \ + use `swap_remove` or `shift_remove` for explicit behavior.")] + pub fn remove(self) -> V { + self.swap_remove() + } + + /// Remove the key, value pair stored in the map for this entry, and return the value. + /// + /// Like [`Vec::swap_remove`][crate::Vec::swap_remove], the pair is removed by swapping it with + /// the last element of the map and popping it off. + /// **This perturbs the position of what used to be the last element!** + /// + /// Computes in **O(1)** time (average). + pub fn swap_remove(self) -> V { + self.swap_remove_entry().1 + } + + /// Remove the key, value pair stored in the map for this entry, and return the value. + /// + /// Like [`Vec::remove`][crate::Vec::remove], the pair is removed by shifting all of the + /// elements that follow it, preserving their relative order. + /// **This perturbs the index of all of those elements!** + /// + /// Computes in **O(n)** time (average). + pub fn shift_remove(self) -> V { + self.shift_remove_entry().1 + } + + /// Remove and return the key, value pair stored in the map for this entry + /// + /// **NOTE:** This is equivalent to [`.swap_remove_entry()`][Self::swap_remove_entry], + /// replacing this entry's position with the last element, and it is deprecated in favor of + /// calling that explicitly. If you need to preserve the relative order of the keys in the map, + /// use [`.shift_remove_entry()`][Self::shift_remove_entry] instead. + #[deprecated(note = "`remove_entry` disrupts the map order -- \ + use `swap_remove_entry` or `shift_remove_entry` for explicit behavior.")] + pub fn remove_entry(self) -> (K, V) { + self.swap_remove_entry() + } + + /// Remove and return the key, value pair stored in the map for this entry + /// + /// Like [`Vec::swap_remove`][crate::Vec::swap_remove], the pair is removed by swapping it with + /// the last element of the map and popping it off. + /// **This perturbs the position of what used to be the last element!** + /// + /// Computes in **O(1)** time (average). + pub fn swap_remove_entry(self) -> (K, V) { + let (map, index) = self.raw.remove_index(); + map.swap_remove_finish(index) + } + + /// Remove and return the key, value pair stored in the map for this entry + /// + /// Like [`Vec::remove`][crate::Vec::remove], the pair is removed by shifting all of the + /// elements that follow it, preserving their relative order. + /// **This perturbs the index of all of those elements!** + /// + /// Computes in **O(n)** time (average). + pub fn shift_remove_entry(self) -> (K, V) { + let (map, index) = self.raw.remove_index(); + map.shift_remove_finish(index) + } +} + +impl fmt::Debug for OccupiedEntry<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(stringify!(OccupiedEntry)) + .field("key", self.key()) + .field("value", self.get()) + .finish() + } +} + +/// A view into a vacant entry in an [`IndexMap`][crate::IndexMap]. +/// It is part of the [`Entry`] enum. +pub struct VacantEntry<'a, K, V> { + map: &'a mut IndexMapCore, + hash: HashValue, + key: K, +} + +impl<'a, K, V> VacantEntry<'a, K, V> { + /// Return the index where a key-value pair may be inserted. + pub fn index(&self) -> usize { + self.map.indices.len() + } + + /// Gets a reference to the key that was used to find the entry. + pub fn key(&self) -> &K { + &self.key + } + + /// Takes ownership of the key, leaving the entry vacant. + pub fn into_key(self) -> K { + self.key + } + + /// 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); + &mut map.entries[i].value + } +} + +impl fmt::Debug for VacantEntry<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple(stringify!(VacantEntry)) + .field(self.key()) + .finish() + } +} + +/// A view into an occupied entry in an [`IndexMap`][crate::IndexMap] obtained by index. +/// +/// This `struct` is created from the [`get_index_entry`][crate::IndexMap::get_index_entry] method. +pub struct IndexedEntry<'a, K, V> { + map: &'a mut IndexMapCore, + // We have a mutable reference to the map, which keeps the index + // valid and pointing to the correct entry. + index: usize, +} + +impl<'a, K, V> IndexedEntry<'a, K, V> { + pub(crate) fn new(map: &'a mut IndexMapCore, index: usize) -> Self { + Self { map, index } + } + + /// Return the index of the key-value pair + #[inline] + pub fn index(&self) -> usize { + self.index + } + + /// Gets a reference to the entry's key in the map. + pub fn key(&self) -> &K { + &self.map.entries[self.index].key + } + + /// Gets a reference to the entry's value in the map. + pub fn get(&self) -> &V { + &self.map.entries[self.index].value + } + + /// Gets a mutable reference to the entry's value in the map. + /// + /// If you need a reference which may outlive the destruction of the + /// `IndexedEntry` value, see [`into_mut`][Self::into_mut]. + pub fn get_mut(&mut self) -> &mut V { + &mut self.map.entries[self.index].value + } + + /// Sets the value of the entry to `value`, and returns the entry's old value. + pub fn insert(&mut self, value: V) -> V { + mem::replace(self.get_mut(), value) + } + + /// Converts into a mutable reference to the entry's value in the map, + /// with a lifetime bound to the map itself. + pub fn into_mut(self) -> &'a mut V { + &mut self.map.entries[self.index].value + } + + /// Remove and return the key, value pair stored in the map for this entry + /// + /// Like [`Vec::swap_remove`][crate::Vec::swap_remove], the pair is removed by swapping it with + /// the last element of the map and popping it off. + /// **This perturbs the position of what used to be the last element!** + /// + /// Computes in **O(1)** time (average). + pub fn swap_remove_entry(self) -> (K, V) { + self.map.swap_remove_index(self.index).unwrap() + } + + /// Remove and return the key, value pair stored in the map for this entry + /// + /// Like [`Vec::remove`][crate::Vec::remove], the pair is removed by shifting all of the + /// elements that follow it, preserving their relative order. + /// **This perturbs the index of all of those elements!** + /// + /// Computes in **O(n)** time (average). + pub fn shift_remove_entry(self) -> (K, V) { + self.map.shift_remove_index(self.index).unwrap() + } + + /// Remove the key, value pair stored in the map for this entry, and return the value. + /// + /// Like [`Vec::swap_remove`][crate::Vec::swap_remove], the pair is removed by swapping it with + /// the last element of the map and popping it off. + /// **This perturbs the position of what used to be the last element!** + /// + /// Computes in **O(1)** time (average). + pub fn swap_remove(self) -> V { + self.swap_remove_entry().1 + } + + /// Remove the key, value pair stored in the map for this entry, and return the value. + /// + /// Like [`Vec::remove`][crate::Vec::remove], the pair is removed by shifting all of the + /// elements that follow it, preserving their relative order. + /// **This perturbs the index of all of those elements!** + /// + /// Computes in **O(n)** time (average). + pub fn shift_remove(self) -> V { + self.shift_remove_entry().1 + } +} + +impl fmt::Debug for IndexedEntry<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(stringify!(IndexedEntry)) + .field("index", &self.index) + .field("key", self.key()) + .field("value", self.get()) + .finish() + } +} diff --git a/src/map/core/raw.rs b/src/map/core/raw.rs index 45b03a3b..233e41e7 100644 --- a/src/map/core/raw.rs +++ b/src/map/core/raw.rs @@ -2,9 +2,8 @@ //! This module encapsulates the `unsafe` access to `hashbrown::raw::RawTable`, //! mostly in dealing with its bucket "pointers". -use super::{equivalent, get_hash, Bucket, Entry, HashValue, IndexMapCore, VacantEntry}; +use super::{equivalent, get_hash, Bucket, HashValue, IndexMapCore}; use core::fmt; -use core::mem::replace; use hashbrown::raw::RawTable; type RawBucket = hashbrown::raw::Bucket; @@ -74,24 +73,21 @@ impl IndexMapCore { } } - pub(crate) fn entry(&mut self, hash: HashValue, key: K) -> Entry<'_, K, V> - where - K: Eq, - { - let eq = equivalent(&key, &self.entries); + pub(super) fn raw_entry( + &mut self, + hash: HashValue, + mut is_match: impl FnMut(&K) -> bool, + ) -> Result, &mut Self> { + 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) => Entry::Occupied(OccupiedEntry { + Some(raw_bucket) => Ok(RawTableEntry { map: self, raw_bucket, - key, - }), - None => Entry::Vacant(VacantEntry { - map: self, - hash, - key, }), + None => Err(self), } } @@ -102,64 +98,41 @@ impl IndexMapCore { } } -/// A view into an occupied entry in an [`IndexMap`][crate::IndexMap]. -/// It is part of the [`Entry`] enum. +/// A view into an occupied raw entry in an `IndexMap`. // SAFETY: The lifetime of the map reference also constrains the raw bucket, // which is essentially a raw pointer into the map indices. -pub struct OccupiedEntry<'a, K, V> { +pub(super) struct RawTableEntry<'a, K, V> { map: &'a mut IndexMapCore, raw_bucket: RawBucket, - key: K, } // `hashbrown::raw::Bucket` is only `Send`, not `Sync`. // SAFETY: `&self` only accesses the bucket to read it. -unsafe impl Sync for OccupiedEntry<'_, K, V> {} +unsafe impl Sync for RawTableEntry<'_, K, V> {} -// The parent module also adds methods that don't threaten the unsafe encapsulation. -impl<'a, K, V> OccupiedEntry<'a, K, V> { - /// Gets a reference to the entry's key in the map. - /// - /// Note that this is not the key that was used to find the entry. There may be an observable - /// difference if the key type has any distinguishing features outside of `Hash` and `Eq`, like - /// extra fields or the memory address of an allocation. - pub fn key(&self) -> &K { - &self.map.entries[self.index()].key - } - - /// Gets a reference to the entry's value in the map. - pub fn get(&self) -> &V { - &self.map.entries[self.index()].value +impl<'a, K, V> RawTableEntry<'a, K, V> { + /// Return the index of the key-value pair + #[inline] + pub(super) fn index(&self) -> usize { + // SAFETY: we have `&mut map` keeping the bucket stable + unsafe { *self.raw_bucket.as_ref() } } - /// Gets a mutable reference to the entry's value in the map. - /// - /// If you need a reference which may outlive the destruction of the - /// [`Entry`] value, see [`into_mut`][Self::into_mut]. - pub fn get_mut(&mut self) -> &mut V { - let index = self.index(); - &mut self.map.entries[index].value + #[inline] + pub(super) fn bucket(&self) -> &Bucket { + &self.map.entries[self.index()] } - /// Converts into a mutable reference to the entry's value in the map, - /// with a lifetime bound to the map itself. - pub fn into_mut(self) -> &'a mut V { + #[inline] + pub(super) fn bucket_mut(&mut self) -> &mut Bucket { let index = self.index(); - &mut self.map.entries[index].value + &mut self.map.entries[index] } - /// Return the index of the key-value pair #[inline] - pub fn index(&self) -> usize { - // SAFETY: we have `&mut map` keeping the bucket stable - unsafe { *self.raw_bucket.as_ref() } - } - - /// Put the new key in the occupied entry's key slot - pub(crate) fn replace_key(self) -> K { + pub(super) fn into_bucket(self) -> &'a mut Bucket { let index = self.index(); - let old_key = &mut self.map.entries[index].key; - replace(old_key, self.key) + &mut self.map.entries[index] } /// Remove the index from indices, leaving the actual entries to the caller. From e787b3df0bc61f2986dd361e73e171a103a20ee3 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 24 Jan 2024 11:14:42 -0800 Subject: [PATCH 153/236] Add a core method to support set's `replace_full` This may improve performance slightly, because it will perform a hash lookup only once, although `RawTable` does force an early reserve to accomplish that. This is the same trade-off that `insert` makes. This also lets us remove the saved key from `map::OccupiedEntry`, which may benefit all other `entry` use cases where that was unused. --- src/map.rs | 2 +- src/map/core.rs | 27 +++++++++++++++++++++++++++ src/map/core/entry.rs | 9 +-------- src/set.rs | 13 ++++--------- src/set/tests.rs | 12 ++++++++++++ 5 files changed, 45 insertions(+), 18 deletions(-) diff --git a/src/map.rs b/src/map.rs index e46846cd..0a1bcfec 100644 --- a/src/map.rs +++ b/src/map.rs @@ -462,7 +462,7 @@ impl IndexMap where S: BuildHasher, { - fn hash(&self, key: &Q) -> HashValue { + pub(crate) fn hash(&self, key: &Q) -> HashValue { let mut h = self.hash_builder.build_hasher(); key.hash(&mut h); HashValue(h.finish() as usize) diff --git a/src/map/core.rs b/src/map/core.rs index 66644775..cbd42378 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -332,6 +332,33 @@ impl IndexMapCore { } } + /// Same as `insert_full`, except it also replaces the key + pub(crate) fn replace_full( + &mut self, + hash: HashValue, + key: K, + value: V, + ) -> (usize, Option<(K, V)>) + where + K: Eq, + { + match self.find_or_insert(hash, &key) { + Ok(i) => { + let entry = &mut self.entries[i]; + let kv = ( + mem::replace(&mut entry.key, key), + mem::replace(&mut entry.value, value), + ); + (i, Some(kv)) + } + Err(i) => { + debug_assert_eq!(i, self.entries.len()); + self.push_entry(hash, key, value); + (i, None) + } + } + } + /// 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 f98825c1..24131bd6 100644 --- a/src/map/core/entry.rs +++ b/src/map/core/entry.rs @@ -9,7 +9,7 @@ impl IndexMapCore { K: Eq, { match self.raw_entry(hash, |k| *k == key) { - Ok(raw) => Entry::Occupied(OccupiedEntry { raw, key }), + Ok(raw) => Entry::Occupied(OccupiedEntry { raw }), Err(map) => Entry::Vacant(VacantEntry { map, hash, key }), } } @@ -124,7 +124,6 @@ impl fmt::Debug for Entry<'_, K, V> { /// It is part of the [`Entry`] enum. pub struct OccupiedEntry<'a, K, V> { raw: RawTableEntry<'a, K, V>, - key: K, } impl<'a, K, V> OccupiedEntry<'a, K, V> { @@ -162,12 +161,6 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { &mut self.raw.into_bucket().value } - /// Put the new key in the occupied entry's key slot - pub(crate) fn replace_key(mut self) -> K { - let old_key = &mut self.raw.bucket_mut().key; - mem::replace(old_key, self.key) - } - /// Sets the value of the entry to `value`, and returns the entry's old value. pub fn insert(&mut self, value: V) -> V { mem::replace(self.get_mut(), value) diff --git a/src/set.rs b/src/set.rs index ec110b26..e2560843 100644 --- a/src/set.rs +++ b/src/set.rs @@ -368,15 +368,10 @@ where /// /// Computes in **O(1)** time (average). pub fn replace_full(&mut self, value: T) -> (usize, Option) { - use super::map::Entry::*; - - match self.map.entry(value) { - Vacant(e) => { - let index = e.index(); - e.insert(()); - (index, None) - } - Occupied(e) => (e.index(), Some(e.replace_key())), + let hash = self.map.hash(&value); + match self.map.core.replace_full(hash, value, ()) { + (i, Some((replaced, ()))) => (i, Some(replaced)), + (i, None) => (i, None), } } diff --git a/src/set/tests.rs b/src/set/tests.rs index d7bb9de6..a02d8a4f 100644 --- a/src/set/tests.rs +++ b/src/set/tests.rs @@ -233,6 +233,18 @@ fn replace_order() { } } +#[test] +fn replace_change() { + // Check pointers to make sure it really changes + let mut set = indexset!(vec![42]); + let old_ptr = set[0].as_ptr(); + let new = set[0].clone(); + let new_ptr = new.as_ptr(); + assert_ne!(old_ptr, new_ptr); + let replaced = set.replace(new).unwrap(); + assert_eq!(replaced.as_ptr(), old_ptr); +} + #[test] fn grow() { let insert = [0, 4, 2, 12, 8, 7, 11]; From 1f80ad9db09e1bd073dcbf3a6c30a71f1baa2a64 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 16 Jan 2024 15:25:09 -0800 Subject: [PATCH 154/236] Opt-in access to the experimental raw entry API. --- src/map.rs | 1 + src/map/core.rs | 2 + src/map/core/raw_entry_v1.rs | 380 +++++++++++++++++++++++++++++++++++ 3 files changed, 383 insertions(+) create mode 100644 src/map/core/raw_entry_v1.rs diff --git a/src/map.rs b/src/map.rs index 0a1bcfec..b52ad53d 100644 --- a/src/map.rs +++ b/src/map.rs @@ -12,6 +12,7 @@ pub mod serde_seq; #[cfg(test)] mod tests; +pub use self::core::raw_entry_v1::{self, RawEntryV1}; pub use self::core::{Entry, IndexedEntry, OccupiedEntry, VacantEntry}; pub use self::iter::{ Drain, IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Splice, Values, ValuesMut, diff --git a/src/map/core.rs b/src/map/core.rs index cbd42378..2dca04a5 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -10,6 +10,8 @@ mod entry; mod raw; +pub mod raw_entry_v1; + use hashbrown::raw::RawTable; use crate::vec::{self, Vec}; diff --git a/src/map/core/raw_entry_v1.rs b/src/map/core/raw_entry_v1.rs new file mode 100644 index 00000000..63cb7d1c --- /dev/null +++ b/src/map/core/raw_entry_v1.rs @@ -0,0 +1,380 @@ +//! Opt-in access to the experimental raw entry API. +//! +//! This module is designed to mimic the raw entry API of [`HashMap`][std::collections::hash_map], +//! matching its unstable state as of Rust 1.75. See the tracking issue +//! [rust#56167](https://github.com/rust-lang/rust/issues/56167) for more details. +//! +//! The trait [`RawEntryV1`] and the `_v1` suffix on its methods are meant to insulate this for +//! the future, in case later breaking changes are needed. If the standard library stabilizes its +//! `hash_raw_entry` feature (or some replacement), matching *inherent* methods will be added +//! to `IndexMap` without such an opt-in trait. + +use super::raw::RawTableEntry; +use super::{get_hash, IndexMapCore}; +use crate::{Equivalent, HashValue, IndexMap}; +use core::hash::{BuildHasher, Hash, Hasher}; +use core::marker::PhantomData; +use core::mem; + +/// Opt-in access to the experimental raw entry API. +/// +/// See the [`raw_entry_v1`][self] module documentation for more information. +pub trait RawEntryV1: private::Sealed { + /// Creates a raw immutable entry builder for the [`IndexMap`]. + fn raw_entry_v1(&self) -> RawEntryBuilder<'_, K, V, S>; + + /// Creates a raw entry builder for the [`IndexMap`]. + fn raw_entry_mut_v1(&mut self) -> RawEntryBuilderMut<'_, K, V, S>; +} + +impl RawEntryV1 for IndexMap { + fn raw_entry_v1(&self) -> RawEntryBuilder<'_, K, V, S> { + RawEntryBuilder { map: self } + } + + fn raw_entry_mut_v1(&mut self) -> RawEntryBuilderMut<'_, K, V, S> { + RawEntryBuilderMut { map: self } + } +} + +/// A builder for computing where in an [`IndexMap`] a key-value pair would be stored. +/// +/// This `struct` is created by the [`IndexMap::raw_entry_v1`] method, provided by the +/// [`RawEntryV1`] trait. See its documentation for more. +pub struct RawEntryBuilder<'a, K, V, S> { + map: &'a IndexMap, +} + +impl<'a, K, V, S> RawEntryBuilder<'a, K, V, S> { + /// Access an entry by key. + pub fn from_key(self, key: &Q) -> Option<(&'a K, &'a V)> + where + S: BuildHasher, + Q: Hash + Equivalent, + { + self.map.get_key_value(key) + } + + /// Access an entry by a key and its hash. + pub fn from_key_hashed_nocheck(self, hash: u64, key: &Q) -> Option<(&'a K, &'a V)> + where + Q: Equivalent, + { + let hash = HashValue(hash as usize); + let i = self.map.core.get_index_of(hash, key)?; + Some(self.map.core.entries[i].refs()) + } + + /// Access an entry by hash. + pub fn from_hash(self, hash: u64, mut is_match: F) -> Option<(&'a K, &'a V)> + where + F: FnMut(&K) -> bool, + { + let hash = HashValue(hash as usize); + let entries = &*self.map.core.entries; + let eq = move |&i: &usize| is_match(&entries[i].key); + let i = *self.map.core.indices.get(hash.get(), eq)?; + Some(entries[i].refs()) + } +} + +/// A builder for computing where in an [`IndexMap`] a key-value pair would be stored. +/// +/// This `struct` is created by the [`IndexMap::raw_entry_mut_v1`] method, provided by the +/// [`RawEntryV1`] trait. See its documentation for more. +pub struct RawEntryBuilderMut<'a, K, V, S> { + map: &'a mut IndexMap, +} + +impl<'a, K, V, S> RawEntryBuilderMut<'a, K, V, S> { + /// Access an entry by key. + pub fn from_key(self, key: &Q) -> RawEntryMut<'a, K, V, S> + where + S: BuildHasher, + Q: Hash + Equivalent, + { + let hash = self.map.hash(key); + self.from_key_hashed_nocheck(hash.get(), key) + } + + /// Access an entry by a key and its hash. + pub fn from_key_hashed_nocheck(self, hash: u64, key: &Q) -> RawEntryMut<'a, K, V, S> + where + Q: Equivalent, + { + self.from_hash(hash, |k| Q::equivalent(key, k)) + } + + /// Access an entry by hash. + pub fn from_hash(self, hash: u64, is_match: F) -> RawEntryMut<'a, K, V, S> + where + F: FnMut(&K) -> bool, + { + let hash = HashValue(hash as usize); + match self.map.core.raw_entry(hash, is_match) { + Ok(raw) => RawEntryMut::Occupied(RawOccupiedEntryMut { + raw, + hash_builder: PhantomData, + }), + Err(map) => RawEntryMut::Vacant(RawVacantEntryMut { + map, + hash_builder: &self.map.hash_builder, + }), + } + } +} + +/// Raw entry for an existing key-value pair or a vacant location to +/// insert one. +pub enum RawEntryMut<'a, K, V, S> { + /// Existing slot with equivalent key. + Occupied(RawOccupiedEntryMut<'a, K, V, S>), + /// Vacant slot (no equivalent key in the map). + Vacant(RawVacantEntryMut<'a, K, V, S>), +} + +impl<'a, K, V, S> RawEntryMut<'a, K, V, S> { + /// Inserts the given default key and value in the entry if it is vacant and returns mutable + /// references to them. Otherwise mutable references to an already existent pair are returned. + pub fn or_insert(self, default_key: K, default_value: V) -> (&'a mut K, &'a mut V) + where + K: Hash, + S: BuildHasher, + { + match self { + Self::Occupied(entry) => entry.into_key_value_mut(), + Self::Vacant(entry) => entry.insert(default_key, default_value), + } + } + + /// Inserts the result of the `call` function in the entry if it is vacant and returns mutable + /// references to them. Otherwise mutable references to an already existent pair are returned. + pub fn or_insert_with(self, call: F) -> (&'a mut K, &'a mut V) + where + F: FnOnce() -> (K, V), + K: Hash, + S: BuildHasher, + { + match self { + Self::Occupied(entry) => entry.into_key_value_mut(), + Self::Vacant(entry) => { + let (key, value) = call(); + entry.insert(key, value) + } + } + } + + /// Modifies the entry if it is occupied. + pub fn and_modify(mut self, f: F) -> Self + where + F: FnOnce(&mut K, &mut V), + { + if let Self::Occupied(entry) = &mut self { + let (k, v) = entry.get_key_value_mut(); + f(k, v); + } + self + } +} + +/// A raw view into an occupied entry in an [`IndexMap`]. +/// It is part of the [`RawEntryMut`] enum. +pub struct RawOccupiedEntryMut<'a, K, V, S> { + raw: RawTableEntry<'a, K, V>, + hash_builder: PhantomData<&'a S>, +} + +impl<'a, K, V, S> RawOccupiedEntryMut<'a, K, V, S> { + /// Return the index of the key-value pair + #[inline] + pub fn index(&self) -> usize { + self.raw.index() + } + + /// Gets a reference to the entry's key in the map. + /// + /// Note that this is not the key that was used to find the entry. There may be an observable + /// difference if the key type has any distinguishing features outside of `Hash` and `Eq`, like + /// extra fields or the memory address of an allocation. + pub fn key(&self) -> &K { + &self.raw.bucket().key + } + + /// Gets a mutable reference to the entry's key in the map. + /// + /// Note that this is not the key that was used to find the entry. There may be an observable + /// difference if the key type has any distinguishing features outside of `Hash` and `Eq`, like + /// extra fields or the memory address of an allocation. + pub fn key_mut(&mut self) -> &mut K { + &mut self.raw.bucket_mut().key + } + + /// Converts into a mutable reference to the entry's key in the map, + /// with a lifetime bound to the map itself. + /// + /// Note that this is not the key that was used to find the entry. There may be an observable + /// difference if the key type has any distinguishing features outside of `Hash` and `Eq`, like + /// extra fields or the memory address of an allocation. + pub fn into_key(&mut self) -> &mut K { + &mut self.raw.bucket_mut().key + } + + /// Gets a reference to the entry's value in the map. + pub fn get(&self) -> &V { + &self.raw.bucket().value + } + + /// Gets a mutable reference to the entry's value in the map. + /// + /// If you need a reference which may outlive the destruction of the + /// [`RawEntryMut`] value, see [`into_mut`][Self::into_mut]. + pub fn get_mut(&mut self) -> &mut V { + &mut self.raw.bucket_mut().value + } + + /// Converts into a mutable reference to the entry's value in the map, + /// with a lifetime bound to the map itself. + pub fn into_mut(self) -> &'a mut V { + &mut self.raw.into_bucket().value + } + + /// Gets a reference to the entry's key and value in the map. + pub fn get_key_value(&self) -> (&K, &V) { + self.raw.bucket().refs() + } + + /// Gets a reference to the entry's key and value in the map. + pub fn get_key_value_mut(&mut self) -> (&mut K, &mut V) { + self.raw.bucket_mut().muts() + } + + /// Converts into a mutable reference to the entry's key and value in the map, + /// with a lifetime bound to the map itself. + pub fn into_key_value_mut(self) -> (&'a mut K, &'a mut V) { + self.raw.into_bucket().muts() + } + + /// Sets the value of the entry, and returns the entry's old value. + pub fn insert(&mut self, value: V) -> V { + mem::replace(self.get_mut(), value) + } + + /// Sets the key of the entry, and returns the entry's old key. + pub fn insert_key(&mut self, key: K) -> K { + mem::replace(self.key_mut(), key) + } + + /// Remove the key, value pair stored in the map for this entry, and return the value. + /// + /// **NOTE:** This is equivalent to [`.swap_remove()`][Self::swap_remove], replacing this + /// entry's position with the last element, and it is deprecated in favor of calling that + /// explicitly. If you need to preserve the relative order of the keys in the map, use + /// [`.shift_remove()`][Self::shift_remove] instead. + #[deprecated(note = "`remove` disrupts the map order -- \ + use `swap_remove` or `shift_remove` for explicit behavior.")] + pub fn remove(self) -> V { + self.swap_remove() + } + + /// Remove the key, value pair stored in the map for this entry, and return the value. + /// + /// Like [`Vec::swap_remove`][crate::Vec::swap_remove], the pair is removed by swapping it with + /// the last element of the map and popping it off. + /// **This perturbs the position of what used to be the last element!** + /// + /// Computes in **O(1)** time (average). + pub fn swap_remove(self) -> V { + self.swap_remove_entry().1 + } + + /// Remove the key, value pair stored in the map for this entry, and return the value. + /// + /// Like [`Vec::remove`][crate::Vec::remove], the pair is removed by shifting all of the + /// elements that follow it, preserving their relative order. + /// **This perturbs the index of all of those elements!** + /// + /// Computes in **O(n)** time (average). + pub fn shift_remove(self) -> V { + self.shift_remove_entry().1 + } + + /// Remove and return the key, value pair stored in the map for this entry + /// + /// **NOTE:** This is equivalent to [`.swap_remove_entry()`][Self::swap_remove_entry], + /// replacing this entry's position with the last element, and it is deprecated in favor of + /// calling that explicitly. If you need to preserve the relative order of the keys in the map, + /// use [`.shift_remove_entry()`][Self::shift_remove_entry] instead. + #[deprecated(note = "`remove_entry` disrupts the map order -- \ + use `swap_remove_entry` or `shift_remove_entry` for explicit behavior.")] + pub fn remove_entry(self) -> (K, V) { + self.swap_remove_entry() + } + + /// Remove and return the key, value pair stored in the map for this entry + /// + /// Like [`Vec::swap_remove`][crate::Vec::swap_remove], the pair is removed by swapping it with + /// the last element of the map and popping it off. + /// **This perturbs the position of what used to be the last element!** + /// + /// Computes in **O(1)** time (average). + pub fn swap_remove_entry(self) -> (K, V) { + let (map, index) = self.raw.remove_index(); + map.swap_remove_finish(index) + } + + /// Remove and return the key, value pair stored in the map for this entry + /// + /// Like [`Vec::remove`][crate::Vec::remove], the pair is removed by shifting all of the + /// elements that follow it, preserving their relative order. + /// **This perturbs the index of all of those elements!** + /// + /// Computes in **O(n)** time (average). + pub fn shift_remove_entry(self) -> (K, V) { + let (map, index) = self.raw.remove_index(); + map.shift_remove_finish(index) + } +} + +/// A view into a vacant raw entry in an [`IndexMap`]. +/// It is part of the [`RawEntryMut`] enum. +pub struct RawVacantEntryMut<'a, K, V, S> { + map: &'a mut IndexMapCore, + hash_builder: &'a S, +} + +impl<'a, K, V, S> RawVacantEntryMut<'a, K, V, S> { + /// Return the index where a key-value pair may be inserted. + pub fn index(&self) -> usize { + self.map.indices.len() + } + + /// Inserts the given key and value into the map, + /// and returns mutable references to them. + pub fn insert(self, 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.insert_hashed_nocheck(h.finish(), key, value) + } + + /// 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() + } +} + +mod private { + pub trait Sealed {} + + impl Sealed for super::IndexMap {} +} From 5c5d23858179bda67dc00675def4a9ac1ae2e308 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 16 Jan 2024 17:00:44 -0800 Subject: [PATCH 155/236] Rename RawEntryV1 to RawEntryApiV1 --- src/map.rs | 2 +- src/map/core/raw_entry_v1.rs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/map.rs b/src/map.rs index b52ad53d..428fac40 100644 --- a/src/map.rs +++ b/src/map.rs @@ -12,7 +12,7 @@ pub mod serde_seq; #[cfg(test)] mod tests; -pub use self::core::raw_entry_v1::{self, RawEntryV1}; +pub use self::core::raw_entry_v1::{self, RawEntryApiV1}; pub use self::core::{Entry, IndexedEntry, OccupiedEntry, VacantEntry}; pub use self::iter::{ Drain, IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Splice, Values, ValuesMut, diff --git a/src/map/core/raw_entry_v1.rs b/src/map/core/raw_entry_v1.rs index 63cb7d1c..cd42bd3a 100644 --- a/src/map/core/raw_entry_v1.rs +++ b/src/map/core/raw_entry_v1.rs @@ -4,10 +4,10 @@ //! matching its unstable state as of Rust 1.75. See the tracking issue //! [rust#56167](https://github.com/rust-lang/rust/issues/56167) for more details. //! -//! The trait [`RawEntryV1`] and the `_v1` suffix on its methods are meant to insulate this for +//! The trait [`RawEntryApiV1`] and the `_v1` suffix on its methods are meant to insulate this for //! the future, in case later breaking changes are needed. If the standard library stabilizes its -//! `hash_raw_entry` feature (or some replacement), matching *inherent* methods will be added -//! to `IndexMap` without such an opt-in trait. +//! `hash_raw_entry` feature (or some replacement), matching *inherent* methods will be added to +//! `IndexMap` without such an opt-in trait. use super::raw::RawTableEntry; use super::{get_hash, IndexMapCore}; @@ -19,7 +19,7 @@ use core::mem; /// Opt-in access to the experimental raw entry API. /// /// See the [`raw_entry_v1`][self] module documentation for more information. -pub trait RawEntryV1: private::Sealed { +pub trait RawEntryApiV1: private::Sealed { /// Creates a raw immutable entry builder for the [`IndexMap`]. fn raw_entry_v1(&self) -> RawEntryBuilder<'_, K, V, S>; @@ -27,7 +27,7 @@ pub trait RawEntryV1: private::Sealed { fn raw_entry_mut_v1(&mut self) -> RawEntryBuilderMut<'_, K, V, S>; } -impl RawEntryV1 for IndexMap { +impl RawEntryApiV1 for IndexMap { fn raw_entry_v1(&self) -> RawEntryBuilder<'_, K, V, S> { RawEntryBuilder { map: self } } @@ -40,7 +40,7 @@ impl RawEntryV1 for IndexMap { /// A builder for computing where in an [`IndexMap`] a key-value pair would be stored. /// /// This `struct` is created by the [`IndexMap::raw_entry_v1`] method, provided by the -/// [`RawEntryV1`] trait. See its documentation for more. +/// [`RawEntryApiV1`] trait. See its documentation for more. pub struct RawEntryBuilder<'a, K, V, S> { map: &'a IndexMap, } @@ -81,7 +81,7 @@ impl<'a, K, V, S> RawEntryBuilder<'a, K, V, S> { /// A builder for computing where in an [`IndexMap`] a key-value pair would be stored. /// /// This `struct` is created by the [`IndexMap::raw_entry_mut_v1`] method, provided by the -/// [`RawEntryV1`] trait. See its documentation for more. +/// [`RawEntryApiV1`] trait. See its documentation for more. pub struct RawEntryBuilderMut<'a, K, V, S> { map: &'a mut IndexMap, } From e646424753d8df56a8af1205ae87db6bd4d56860 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 23 Jan 2024 18:45:57 -0800 Subject: [PATCH 156/236] Adapt some of hashbrown's raw entry docs as our own --- src/map/core/raw_entry_v1.rs | 136 +++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/src/map/core/raw_entry_v1.rs b/src/map/core/raw_entry_v1.rs index cd42bd3a..51f6d4d5 100644 --- a/src/map/core/raw_entry_v1.rs +++ b/src/map/core/raw_entry_v1.rs @@ -21,9 +21,145 @@ use core::mem; /// See the [`raw_entry_v1`][self] module documentation for more information. pub trait RawEntryApiV1: private::Sealed { /// Creates a raw immutable entry builder for the [`IndexMap`]. + /// + /// Raw entries provide the lowest level of control for searching and + /// manipulating a map. They must be manually initialized with a hash and + /// then manually searched. + /// + /// This is useful for + /// * Hash memoization + /// * Using a search key that doesn't work with the [`Equivalent`] trait + /// * Using custom comparison logic without newtype wrappers + /// + /// Unless you are in such a situation, higher-level and more foolproof APIs like + /// [`get`][IndexMap::get] should be preferred. + /// + /// Immutable raw entries have very limited use; you might instead want + /// [`raw_entry_mut_v1`][Self::raw_entry_mut_v1]. + /// + /// # Examples + /// + /// ``` + /// use core::hash::{BuildHasher, Hash}; + /// use indexmap::map::{IndexMap, RawEntryApiV1}; + /// + /// let mut map = IndexMap::new(); + /// map.extend([("a", 100), ("b", 200), ("c", 300)]); + /// + /// fn compute_hash(hash_builder: &S, key: &K) -> u64 { + /// use core::hash::Hasher; + /// let mut state = hash_builder.build_hasher(); + /// key.hash(&mut state); + /// state.finish() + /// } + /// + /// for k in ["a", "b", "c", "d", "e", "f"] { + /// let hash = compute_hash(map.hasher(), k); + /// let v = map.get(k).cloned(); + /// let kv = v.as_ref().map(|v| (&k, v)); + /// + /// println!("Key: {} and value: {:?}", k, v); + /// + /// assert_eq!(map.raw_entry_v1().from_key(k), kv); + /// assert_eq!(map.raw_entry_v1().from_hash(hash, |q| *q == k), kv); + /// assert_eq!(map.raw_entry_v1().from_key_hashed_nocheck(hash, k), kv); + /// } + /// ``` fn raw_entry_v1(&self) -> RawEntryBuilder<'_, K, V, S>; /// Creates a raw entry builder for the [`IndexMap`]. + /// + /// Raw entries provide the lowest level of control for searching and + /// manipulating a map. They must be manually initialized with a hash and + /// then manually searched. After this, insertions into a vacant entry + /// still require an owned key to be provided. + /// + /// Raw entries are useful for such exotic situations as: + /// + /// * Hash memoization + /// * Deferring the creation of an owned key until it is known to be required + /// * Using a search key that doesn't work with the [`Equivalent`] trait + /// * Using custom comparison logic without newtype wrappers + /// + /// Because raw entries provide much more low-level control, it's much easier + /// to put the `IndexMap` into an inconsistent state which, while memory-safe, + /// will cause the map to produce seemingly random results. Higher-level and more + /// foolproof APIs like [`entry`][IndexMap::entry] should be preferred when possible. + /// + /// Raw entries give mutable access to the keys. This must not be used + /// to modify how the key would compare or hash, as the map will not re-evaluate + /// where the key should go, meaning the keys may become "lost" if their + /// location does not reflect their state. For instance, if you change a key + /// so that the map now contains keys which compare equal, search may start + /// acting erratically, with two keys randomly masking each other. Implementations + /// are free to assume this doesn't happen (within the limits of memory-safety). + /// + /// # Examples + /// + /// ``` + /// use core::hash::{BuildHasher, Hash}; + /// use indexmap::map::{IndexMap, RawEntryApiV1}; + /// use indexmap::map::raw_entry_v1::RawEntryMut; + /// + /// let mut map = IndexMap::new(); + /// map.extend([("a", 100), ("b", 200), ("c", 300)]); + /// + /// fn compute_hash(hash_builder: &S, key: &K) -> u64 { + /// use core::hash::Hasher; + /// let mut state = hash_builder.build_hasher(); + /// key.hash(&mut state); + /// state.finish() + /// } + /// + /// // Existing key (insert and update) + /// match map.raw_entry_mut_v1().from_key("a") { + /// RawEntryMut::Vacant(_) => unreachable!(), + /// RawEntryMut::Occupied(mut view) => { + /// assert_eq!(view.get(), &100); + /// let v = view.get_mut(); + /// let new_v = (*v) * 10; + /// *v = new_v; + /// assert_eq!(view.insert(1111), 1000); + /// } + /// } + /// + /// assert_eq!(map["a"], 1111); + /// assert_eq!(map.len(), 3); + /// + /// // Existing key (take) + /// let hash = compute_hash(map.hasher(), "c"); + /// match map.raw_entry_mut_v1().from_key_hashed_nocheck(hash, "c") { + /// RawEntryMut::Vacant(_) => unreachable!(), + /// RawEntryMut::Occupied(view) => { + /// assert_eq!(view.shift_remove_entry(), ("c", 300)); + /// } + /// } + /// assert_eq!(map.raw_entry_v1().from_key("c"), None); + /// assert_eq!(map.len(), 2); + /// + /// // Nonexistent key (insert and update) + /// let key = "d"; + /// let hash = compute_hash(map.hasher(), key); + /// match map.raw_entry_mut_v1().from_hash(hash, |q| *q == key) { + /// RawEntryMut::Occupied(_) => unreachable!(), + /// RawEntryMut::Vacant(view) => { + /// let (k, value) = view.insert("d", 4000); + /// assert_eq!((*k, *value), ("d", 4000)); + /// *value = 40000; + /// } + /// } + /// assert_eq!(map["d"], 40000); + /// assert_eq!(map.len(), 3); + /// + /// match map.raw_entry_mut_v1().from_hash(hash, |q| *q == key) { + /// RawEntryMut::Vacant(_) => unreachable!(), + /// RawEntryMut::Occupied(view) => { + /// assert_eq!(view.swap_remove_entry(), ("d", 40000)); + /// } + /// } + /// assert_eq!(map.get("d"), None); + /// assert_eq!(map.len(), 2); + /// ``` fn raw_entry_mut_v1(&mut self) -> RawEntryBuilderMut<'_, K, V, S>; } From 54c487e36cad2ea3d9c1b1f8f382a72edf2759c4 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 24 Jan 2024 12:57:02 -0800 Subject: [PATCH 157/236] Add raw entry `Debug` --- src/map/core/raw_entry_v1.rs | 39 ++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/map/core/raw_entry_v1.rs b/src/map/core/raw_entry_v1.rs index 51f6d4d5..bdc19d87 100644 --- a/src/map/core/raw_entry_v1.rs +++ b/src/map/core/raw_entry_v1.rs @@ -12,6 +12,7 @@ use super::raw::RawTableEntry; use super::{get_hash, IndexMapCore}; use crate::{Equivalent, HashValue, IndexMap}; +use core::fmt; use core::hash::{BuildHasher, Hash, Hasher}; use core::marker::PhantomData; use core::mem; @@ -181,6 +182,12 @@ pub struct RawEntryBuilder<'a, K, V, S> { map: &'a IndexMap, } +impl fmt::Debug for RawEntryBuilder<'_, K, V, S> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("RawEntryBuilder").finish_non_exhaustive() + } +} + impl<'a, K, V, S> RawEntryBuilder<'a, K, V, S> { /// Access an entry by key. pub fn from_key(self, key: &Q) -> Option<(&'a K, &'a V)> @@ -222,6 +229,12 @@ pub struct RawEntryBuilderMut<'a, K, V, S> { map: &'a mut IndexMap, } +impl fmt::Debug for RawEntryBuilderMut<'_, K, V, S> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("RawEntryBuilderMut").finish_non_exhaustive() + } +} + impl<'a, K, V, S> RawEntryBuilderMut<'a, K, V, S> { /// Access an entry by key. pub fn from_key(self, key: &Q) -> RawEntryMut<'a, K, V, S> @@ -269,6 +282,17 @@ pub enum RawEntryMut<'a, K, V, S> { Vacant(RawVacantEntryMut<'a, K, V, S>), } +impl fmt::Debug for RawEntryMut<'_, K, V, S> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut tuple = f.debug_tuple("RawEntryMut"); + match self { + Self::Vacant(v) => tuple.field(v), + Self::Occupied(o) => tuple.field(o), + }; + tuple.finish() + } +} + impl<'a, K, V, S> RawEntryMut<'a, K, V, S> { /// Inserts the given default key and value in the entry if it is vacant and returns mutable /// references to them. Otherwise mutable references to an already existent pair are returned. @@ -320,6 +344,15 @@ pub struct RawOccupiedEntryMut<'a, K, V, S> { hash_builder: PhantomData<&'a S>, } +impl fmt::Debug for RawOccupiedEntryMut<'_, K, V, S> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("RawOccupiedEntryMut") + .field("key", self.key()) + .field("value", self.get()) + .finish_non_exhaustive() + } +} + impl<'a, K, V, S> RawOccupiedEntryMut<'a, K, V, S> { /// Return the index of the key-value pair #[inline] @@ -478,6 +511,12 @@ pub struct RawVacantEntryMut<'a, K, V, S> { hash_builder: &'a S, } +impl fmt::Debug for RawVacantEntryMut<'_, K, V, S> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("RawVacantEntryMut").finish_non_exhaustive() + } +} + impl<'a, K, V, S> RawVacantEntryMut<'a, K, V, S> { /// Return the index where a key-value pair may be inserted. pub fn index(&self) -> usize { From 77382d6522a32f00b6aafc43109a55dd4bc2f0c4 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 24 Jan 2024 13:00:35 -0800 Subject: [PATCH 158/236] Remove unnecessary `stringify!` --- src/map/core/entry.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/map/core/entry.rs b/src/map/core/entry.rs index 24131bd6..ada26ef7 100644 --- a/src/map/core/entry.rs +++ b/src/map/core/entry.rs @@ -113,10 +113,12 @@ impl<'a, K, V> Entry<'a, K, V> { impl fmt::Debug for Entry<'_, K, V> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - Entry::Vacant(ref v) => f.debug_tuple(stringify!(Entry)).field(v).finish(), - Entry::Occupied(ref o) => f.debug_tuple(stringify!(Entry)).field(o).finish(), - } + let mut tuple = f.debug_tuple("Entry"); + match self { + Entry::Vacant(v) => tuple.field(v), + Entry::Occupied(o) => tuple.field(o), + }; + tuple.finish() } } @@ -239,7 +241,7 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { impl fmt::Debug for OccupiedEntry<'_, K, V> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct(stringify!(OccupiedEntry)) + f.debug_struct("OccupiedEntry") .field("key", self.key()) .field("value", self.get()) .finish() @@ -284,9 +286,7 @@ impl<'a, K, V> VacantEntry<'a, K, V> { impl fmt::Debug for VacantEntry<'_, K, V> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple(stringify!(VacantEntry)) - .field(self.key()) - .finish() + f.debug_tuple("VacantEntry").field(self.key()).finish() } } @@ -387,7 +387,7 @@ impl<'a, K, V> IndexedEntry<'a, K, V> { impl fmt::Debug for IndexedEntry<'_, K, V> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct(stringify!(IndexedEntry)) + f.debug_struct("IndexedEntry") .field("index", &self.index) .field("key", self.key()) .field("value", self.get()) From d84bf5e998130c6d833f526d46b2e18366b7fade Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sat, 27 Jan 2024 16:47:59 -0800 Subject: [PATCH 159/236] Release 2.2.0 --- Cargo.toml | 2 +- RELEASES.md | 26 +++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 284de4a4..41c4b60a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "indexmap" edition = "2021" -version = "2.1.0" +version = "2.2.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 6d9cf14a..92811bfc 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,27 @@ +- 2.2.0 + + - The new `IndexMap::get_index_entry` method finds an entry by its index for + in-place manipulation. + + - The `Keys` iterator now implements `Index` for quick access to the + entry's key, compared to indexing the map to get the value. + + - The new `IndexMap::splice` and `IndexSet::splice` methods will drain the + given range as an iterator, and then replace that range with entries from + an input iterator. + + - The new trait `RawEntryApiV1` offers opt-in access to a raw entry API for + `IndexMap`, corresponding to the unstable API on `HashSet` as of Rust 1.75. + + - Many `IndexMap` and `IndexSet` methods have relaxed their type constraints, + e.g. removing `K: Hash` on methods that don't actually need to hash. + + - Removal methods `remove`, `remove_entry`, and `take` are now deprecated + in favor of their `shift_` or `swap_` prefixed variants, which are more + explicit about their effect on the index and order of remaining items. + The deprecated methods will remain to guide drop-in replacements from + `HashMap` and `HashSet` toward the prefixed methods. + - 2.1.0 - Empty slices can now be created with `map::Slice::{new, new_mut}` and @@ -6,7 +30,7 @@ - `IndexMap`, `IndexSet`, and their respective `Slice`s all have binary search methods for sorted data: map `binary_search_keys` and set - `binary_search` for plain comparision, `binary_search_by` for custom + `binary_search` for plain comparison, `binary_search_by` for custom comparators, `binary_search_by_key` for key extraction, and `partition_point` for boolean conditions. From 904689f084d520b143db05450c6797e837f3f89d Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sun, 28 Jan 2024 10:07:04 -0800 Subject: [PATCH 160/236] ci: upgrade to actions/checkout@v4 --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3eebe4db..592b8337 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: bench: test build benchmarks steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} @@ -66,7 +66,7 @@ jobs: target: thumbv6m-none-eabi steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} @@ -79,7 +79,7 @@ jobs: clippy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@beta with: components: clippy @@ -88,7 +88,7 @@ jobs: miri: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly with: components: miri @@ -98,7 +98,7 @@ jobs: name: Check MSRV and minimal-versions runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: nightly From 382a45c8856422c6889c2d32f3c9ce9ba22d2450 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sun, 28 Jan 2024 10:07:31 -0800 Subject: [PATCH 161/236] Upgrade dev-dep itertools to 0.12 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 41c4b60a..d4903ad3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ default-features = false features = ["raw"] [dev-dependencies] -itertools = "0.11" +itertools = "0.12" rand = {version = "0.8", features = ["small_rng"] } quickcheck = { version = "1.0", default-features = false } fnv = "1.0" From 34a7af7106642bfc376e74e8ba3b596b3d20a4fa Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sun, 28 Jan 2024 10:25:55 -0800 Subject: [PATCH 162/236] Allow `clippy::style` --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index d4903ad3..bf4fe206 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,3 +59,6 @@ rustdoc-args = ["--cfg", "docsrs"] [workspace] members = ["test-nostd", "test-serde"] + +[lints.clippy] +style = "allow" From 244bd814c20cc5c69549d3cf139ae7d5364e4e87 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sun, 28 Jan 2024 16:28:54 -0800 Subject: [PATCH 163/236] Fix `RawOccupiedEntryMut::into_key` --- Cargo.toml | 2 +- RELEASES.md | 6 ++++++ src/map/core/raw_entry_v1.rs | 4 ++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bf4fe206..1bbf4940 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "indexmap" edition = "2021" -version = "2.2.0" +version = "2.2.1" 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 92811bfc..50551186 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,9 @@ +- 2.2.1 + + - Corrected the signature of `RawOccupiedEntryMut::into_key(self) -> &'a mut K`, + This a breaking change from 2.2.0, but that version was published for less + than a day and has now been yanked. + - 2.2.0 - The new `IndexMap::get_index_entry` method finds an entry by its index for diff --git a/src/map/core/raw_entry_v1.rs b/src/map/core/raw_entry_v1.rs index bdc19d87..ad27cf03 100644 --- a/src/map/core/raw_entry_v1.rs +++ b/src/map/core/raw_entry_v1.rs @@ -384,8 +384,8 @@ impl<'a, K, V, S> RawOccupiedEntryMut<'a, K, V, S> { /// Note that this is not the key that was used to find the entry. There may be an observable /// difference if the key type has any distinguishing features outside of `Hash` and `Eq`, like /// extra fields or the memory address of an allocation. - pub fn into_key(&mut self) -> &mut K { - &mut self.raw.bucket_mut().key + pub fn into_key(self) -> &'a mut K { + &mut self.raw.into_bucket().key } /// Gets a reference to the entry's value in the map. From 54a42ec33b03e9a27f4a9369ebbb58b6478505a3 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 30 Jan 2024 12:58:50 -0800 Subject: [PATCH 164/236] Add indexing methods to raw entry The `std`-matching methods of `RawEntryBuilder` only return `(&K, &V)`, but the index is also useful when working with `IndexMap`. ```rust impl<'a, K, V, S> RawEntryBuilder<'a, K, V, S> { /// Access an entry by hash, including its index. pub fn from_hash_full(self, hash: u64, is_match: F) -> Option<(usize, &'a K, &'a V)> where F: FnMut(&K) -> bool, /// Access the index of an entry by hash. pub fn index_from_hash(self, hash: u64, is_match: F) -> Option where F: FnMut(&K) -> bool, } ``` In addition, the `RawEntryMut` enum can report its index by forwarding to the existing methods on its variants. ```rust impl<'a, K, V, S> RawEntryMut<'a, K, V, S> { /// Return the index where the key-value pair exists or may be inserted. pub fn index(&self) -> usize } ``` --- src/map/core/raw_entry_v1.rs | 49 +++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/src/map/core/raw_entry_v1.rs b/src/map/core/raw_entry_v1.rs index ad27cf03..68cdc741 100644 --- a/src/map/core/raw_entry_v1.rs +++ b/src/map/core/raw_entry_v1.rs @@ -56,14 +56,18 @@ pub trait RawEntryApiV1: private::Sealed { /// /// for k in ["a", "b", "c", "d", "e", "f"] { /// let hash = compute_hash(map.hasher(), k); - /// let v = map.get(k).cloned(); - /// let kv = v.as_ref().map(|v| (&k, v)); + /// let i = map.get_index_of(k); + /// let v = map.get(k); + /// let kv = map.get_key_value(k); + /// let ikv = map.get_full(k); /// /// println!("Key: {} and value: {:?}", k, v); /// /// assert_eq!(map.raw_entry_v1().from_key(k), kv); /// assert_eq!(map.raw_entry_v1().from_hash(hash, |q| *q == k), kv); /// assert_eq!(map.raw_entry_v1().from_key_hashed_nocheck(hash, k), kv); + /// assert_eq!(map.raw_entry_v1().from_hash_full(hash, |q| *q == k), ikv); + /// assert_eq!(map.raw_entry_v1().index_from_hash(hash, |q| *q == k), i); /// } /// ``` fn raw_entry_v1(&self) -> RawEntryBuilder<'_, K, V, S>; @@ -116,6 +120,7 @@ pub trait RawEntryApiV1: private::Sealed { /// match map.raw_entry_mut_v1().from_key("a") { /// RawEntryMut::Vacant(_) => unreachable!(), /// RawEntryMut::Occupied(mut view) => { + /// assert_eq!(view.index(), 0); /// assert_eq!(view.get(), &100); /// let v = view.get_mut(); /// let new_v = (*v) * 10; @@ -132,6 +137,7 @@ pub trait RawEntryApiV1: private::Sealed { /// match map.raw_entry_mut_v1().from_key_hashed_nocheck(hash, "c") { /// RawEntryMut::Vacant(_) => unreachable!(), /// RawEntryMut::Occupied(view) => { + /// assert_eq!(view.index(), 2); /// assert_eq!(view.shift_remove_entry(), ("c", 300)); /// } /// } @@ -144,6 +150,7 @@ pub trait RawEntryApiV1: private::Sealed { /// match map.raw_entry_mut_v1().from_hash(hash, |q| *q == key) { /// RawEntryMut::Occupied(_) => unreachable!(), /// RawEntryMut::Vacant(view) => { + /// assert_eq!(view.index(), 2); /// let (k, value) = view.insert("d", 4000); /// assert_eq!((*k, *value), ("d", 4000)); /// *value = 40000; @@ -155,6 +162,7 @@ pub trait RawEntryApiV1: private::Sealed { /// match map.raw_entry_mut_v1().from_hash(hash, |q| *q == key) { /// RawEntryMut::Vacant(_) => unreachable!(), /// RawEntryMut::Occupied(view) => { + /// assert_eq!(view.index(), 2); /// assert_eq!(view.swap_remove_entry(), ("d", 40000)); /// } /// } @@ -205,19 +213,39 @@ impl<'a, K, V, S> RawEntryBuilder<'a, K, V, S> { { let hash = HashValue(hash as usize); let i = self.map.core.get_index_of(hash, key)?; - Some(self.map.core.entries[i].refs()) + self.map.get_index(i) } /// Access an entry by hash. - pub fn from_hash(self, hash: u64, mut is_match: F) -> Option<(&'a K, &'a V)> + pub fn from_hash(self, hash: u64, is_match: F) -> Option<(&'a K, &'a V)> + where + F: FnMut(&K) -> bool, + { + let map = self.map; + let i = self.index_from_hash(hash, is_match)?; + map.get_index(i) + } + + /// Access an entry by hash, including its index. + pub fn from_hash_full(self, hash: u64, is_match: F) -> Option<(usize, &'a K, &'a V)> + where + F: FnMut(&K) -> bool, + { + let map = self.map; + let i = self.index_from_hash(hash, is_match)?; + let (key, value) = map.get_index(i)?; + Some((i, key, value)) + } + + /// Access the index of an entry by hash. + pub fn index_from_hash(self, hash: u64, mut is_match: F) -> Option where F: FnMut(&K) -> bool, { let hash = HashValue(hash as usize); let entries = &*self.map.core.entries; let eq = move |&i: &usize| is_match(&entries[i].key); - let i = *self.map.core.indices.get(hash.get(), eq)?; - Some(entries[i].refs()) + self.map.core.indices.get(hash.get(), eq).copied() } } @@ -294,6 +322,15 @@ impl fmt::Debug for RawEntryMut<'_, K, V, S> { } impl<'a, K, V, S> RawEntryMut<'a, K, V, S> { + /// Return the index where the key-value pair exists or may be inserted. + #[inline] + pub fn index(&self) -> usize { + match self { + Self::Occupied(entry) => entry.index(), + Self::Vacant(entry) => entry.index(), + } + } + /// Inserts the given default key and value in the entry if it is vacant and returns mutable /// references to them. Otherwise mutable references to an already existent pair are returned. pub fn or_insert(self, default_key: K, default_value: V) -> (&'a mut K, &'a mut V) From f4bb006d92901b920cd9f387e634d01af11b9df8 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 31 Jan 2024 11:26:47 -0800 Subject: [PATCH 165/236] Release 2.2.2 --- Cargo.toml | 2 +- RELEASES.md | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1bbf4940..022a2588 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "indexmap" edition = "2021" -version = "2.2.1" +version = "2.2.2" 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 50551186..15000bef 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,8 @@ +- 2.2.2 + + - Added indexing methods to raw entries: `RawEntryBuilder::from_hash_full`, + `RawEntryBuilder::index_from_hash`, and `RawEntryMut::index`. + - 2.2.1 - Corrected the signature of `RawOccupiedEntryMut::into_key(self) -> &'a mut K`, From 685c8ff913d4148d99adffbb869ac231bb8bf62a Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Thu, 1 Feb 2024 09:32:16 +0100 Subject: [PATCH 166/236] Releases: Adjust markdown format When services like https://github.com/renovatebot/renovate open dependency update pull requests they try to find a corresponding changelog entry to let the developers know what changes this dependency update includes. The current format of the `RELEASES.md` file with its nested lists appears to be uncommon enough to not get recognized by at least renovatebot. This changes the format to use `h2` elements for the versions, which should allow renovatebot to pick up the contents and generally renders in a slightly nicer way :D --- RELEASES.md | 580 ++++++++++++++++++++++++++-------------------------- 1 file changed, 291 insertions(+), 289 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 15000bef..589a93a7 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,159 +1,161 @@ -- 2.2.2 +# Releases - - Added indexing methods to raw entries: `RawEntryBuilder::from_hash_full`, - `RawEntryBuilder::index_from_hash`, and `RawEntryMut::index`. +## 2.2.2 -- 2.2.1 +- Added indexing methods to raw entries: `RawEntryBuilder::from_hash_full`, + `RawEntryBuilder::index_from_hash`, and `RawEntryMut::index`. - - Corrected the signature of `RawOccupiedEntryMut::into_key(self) -> &'a mut K`, - This a breaking change from 2.2.0, but that version was published for less - than a day and has now been yanked. +## 2.2.1 -- 2.2.0 +- Corrected the signature of `RawOccupiedEntryMut::into_key(self) -> &'a mut K`, + This a breaking change from 2.2.0, but that version was published for less + than a day and has now been yanked. - - The new `IndexMap::get_index_entry` method finds an entry by its index for - in-place manipulation. +## 2.2.0 - - The `Keys` iterator now implements `Index` for quick access to the - entry's key, compared to indexing the map to get the value. +- The new `IndexMap::get_index_entry` method finds an entry by its index for + in-place manipulation. - - The new `IndexMap::splice` and `IndexSet::splice` methods will drain the - given range as an iterator, and then replace that range with entries from - an input iterator. +- The `Keys` iterator now implements `Index` for quick access to the + entry's key, compared to indexing the map to get the value. - - The new trait `RawEntryApiV1` offers opt-in access to a raw entry API for - `IndexMap`, corresponding to the unstable API on `HashSet` as of Rust 1.75. +- The new `IndexMap::splice` and `IndexSet::splice` methods will drain the + given range as an iterator, and then replace that range with entries from + an input iterator. - - Many `IndexMap` and `IndexSet` methods have relaxed their type constraints, - e.g. removing `K: Hash` on methods that don't actually need to hash. +- The new trait `RawEntryApiV1` offers opt-in access to a raw entry API for + `IndexMap`, corresponding to the unstable API on `HashSet` as of Rust 1.75. - - Removal methods `remove`, `remove_entry`, and `take` are now deprecated - in favor of their `shift_` or `swap_` prefixed variants, which are more - explicit about their effect on the index and order of remaining items. - The deprecated methods will remain to guide drop-in replacements from - `HashMap` and `HashSet` toward the prefixed methods. +- Many `IndexMap` and `IndexSet` methods have relaxed their type constraints, + e.g. removing `K: Hash` on methods that don't actually need to hash. -- 2.1.0 +- Removal methods `remove`, `remove_entry`, and `take` are now deprecated + in favor of their `shift_` or `swap_` prefixed variants, which are more + explicit about their effect on the index and order of remaining items. + The deprecated methods will remain to guide drop-in replacements from + `HashMap` and `HashSet` toward the prefixed methods. - - Empty slices can now be created with `map::Slice::{new, new_mut}` and - `set::Slice::new`. In addition, `Slice::new`, `len`, and `is_empty` are - now `const` functions on both types. +## 2.1.0 - - `IndexMap`, `IndexSet`, and their respective `Slice`s all have binary - search methods for sorted data: map `binary_search_keys` and set - `binary_search` for plain comparison, `binary_search_by` for custom - comparators, `binary_search_by_key` for key extraction, and - `partition_point` for boolean conditions. +- Empty slices can now be created with `map::Slice::{new, new_mut}` and + `set::Slice::new`. In addition, `Slice::new`, `len`, and `is_empty` are + now `const` functions on both types. -- 2.0.2 +- `IndexMap`, `IndexSet`, and their respective `Slice`s all have binary + search methods for sorted data: map `binary_search_keys` and set + `binary_search` for plain comparison, `binary_search_by` for custom + comparators, `binary_search_by_key` for key extraction, and + `partition_point` for boolean conditions. - - The `hashbrown` dependency has been updated to version 0.14.1 to - complete the support for Rust 1.63. +## 2.0.2 -- 2.0.1 +- The `hashbrown` dependency has been updated to version 0.14.1 to + complete the support for Rust 1.63. - - **MSRV**: Rust 1.63.0 is now supported as well, pending publication of - `hashbrown`'s relaxed MSRV (or use cargo `--ignore-rust-version`). +## 2.0.1 -- 2.0.0 +- **MSRV**: Rust 1.63.0 is now supported as well, pending publication of + `hashbrown`'s relaxed MSRV (or use cargo `--ignore-rust-version`). - - **MSRV**: Rust 1.64.0 or later is now required. +## 2.0.0 - - The `"std"` feature is no longer auto-detected. It is included in the - default feature set, or else can be enabled like any other Cargo feature. +- **MSRV**: Rust 1.64.0 or later is now required. - - The `"serde-1"` feature has been removed, leaving just the optional - `"serde"` dependency to be enabled like a feature itself. +- The `"std"` feature is no longer auto-detected. It is included in the + default feature set, or else can be enabled like any other Cargo feature. - - `IndexMap::get_index_mut` now returns `Option<(&K, &mut V)>`, changing - the key part from `&mut K` to `&K`. There is also a new alternative - `MutableKeys::get_index_mut2` to access the former behavior. +- The `"serde-1"` feature has been removed, leaving just the optional + `"serde"` dependency to be enabled like a feature itself. - - The new `map::Slice` and `set::Slice` offer a linear view of maps - and sets, behaving a lot like normal `[(K, V)]` and `[T]` slices. Notably, - comparison traits like `Eq` only consider items in order, rather than hash - lookups, and slices even implement `Hash`. +- `IndexMap::get_index_mut` now returns `Option<(&K, &mut V)>`, changing + the key part from `&mut K` to `&K`. There is also a new alternative + `MutableKeys::get_index_mut2` to access the former behavior. - - `IndexMap` and `IndexSet` now have `sort_by_cached_key` and - `par_sort_by_cached_key` methods which perform stable sorts in place - using a key extraction function. +- The new `map::Slice` and `set::Slice` offer a linear view of maps + and sets, behaving a lot like normal `[(K, V)]` and `[T]` slices. Notably, + comparison traits like `Eq` only consider items in order, rather than hash + lookups, and slices even implement `Hash`. - - `IndexMap` and `IndexSet` now have `reserve_exact`, `try_reserve`, and - `try_reserve_exact` methods that correspond to the same methods on `Vec`. - However, exactness only applies to the direct capacity for items, while the - raw hash table still follows its own rules for capacity and load factor. +- `IndexMap` and `IndexSet` now have `sort_by_cached_key` and + `par_sort_by_cached_key` methods which perform stable sorts in place + using a key extraction function. - - The `Equivalent` trait is now re-exported from the `equivalent` crate, - intended as a common base to allow types to work with multiple map types. +- `IndexMap` and `IndexSet` now have `reserve_exact`, `try_reserve`, and + `try_reserve_exact` methods that correspond to the same methods on `Vec`. + However, exactness only applies to the direct capacity for items, while the + raw hash table still follows its own rules for capacity and load factor. - - The `hashbrown` dependency has been updated to version 0.14. +- The `Equivalent` trait is now re-exported from the `equivalent` crate, + intended as a common base to allow types to work with multiple map types. - - The `serde_seq` module has been moved from the crate root to below the - `map` module. +- The `hashbrown` dependency has been updated to version 0.14. -- 1.9.3 +- The `serde_seq` module has been moved from the crate root to below the + `map` module. - - Bump the `rustc-rayon` dependency, for compiler use only. +## 1.9.3 -- 1.9.2 +- Bump the `rustc-rayon` dependency, for compiler use only. - - `IndexMap` and `IndexSet` both implement `arbitrary::Arbitrary<'_>` and - `quickcheck::Arbitrary` if those optional dependency features are enabled. +## 1.9.2 -- 1.9.1 +- `IndexMap` and `IndexSet` both implement `arbitrary::Arbitrary<'_>` and + `quickcheck::Arbitrary` if those optional dependency features are enabled. - - The MSRV now allows Rust 1.56.0 as well. However, currently `hashbrown` - 0.12.1 requires 1.56.1, so users on 1.56.0 should downgrade that to 0.12.0 - until there is a later published version relaxing its requirement. +## 1.9.1 -- 1.9.0 +- The MSRV now allows Rust 1.56.0 as well. However, currently `hashbrown` + 0.12.1 requires 1.56.1, so users on 1.56.0 should downgrade that to 0.12.0 + until there is a later published version relaxing its requirement. - - **MSRV**: Rust 1.56.1 or later is now required. +## 1.9.0 - - The `hashbrown` dependency has been updated to version 0.12. +- **MSRV**: Rust 1.56.1 or later is now required. - - `IterMut` and `ValuesMut` now implement `Debug`. +- The `hashbrown` dependency has been updated to version 0.12. - - The new `IndexMap::shrink_to` and `IndexSet::shrink_to` methods shrink - the capacity with a lower bound. +- `IterMut` and `ValuesMut` now implement `Debug`. - - The new `IndexMap::move_index` and `IndexSet::move_index` methods change - the position of an item from one index to another, shifting the items - between to accommodate the move. +- The new `IndexMap::shrink_to` and `IndexSet::shrink_to` methods shrink + the capacity with a lower bound. -- 1.8.2 +- The new `IndexMap::move_index` and `IndexSet::move_index` methods change + the position of an item from one index to another, shifting the items + between to accommodate the move. - - Bump the `rustc-rayon` dependency, for compiler use only. +## 1.8.2 -- 1.8.1 +- Bump the `rustc-rayon` dependency, for compiler use only. - - The new `IndexSet::replace_full` will return the index of the item along - with the replaced value, if any, by @zakcutner in PR [222]. +## 1.8.1 + +- The new `IndexSet::replace_full` will return the index of the item along + with the replaced value, if any, by @zakcutner in PR [222]. [222]: https://github.com/indexmap-rs/indexmap/pull/222 -- 1.8.0 +## 1.8.0 - - The new `IndexMap::into_keys` and `IndexMap::into_values` will consume - the map into keys or values, respectively, matching Rust 1.54's `HashMap` - methods, by @taiki-e in PR [195]. +- The new `IndexMap::into_keys` and `IndexMap::into_values` will consume + the map into keys or values, respectively, matching Rust 1.54's `HashMap` + methods, by @taiki-e in PR [195]. - - More of the iterator types implement `Debug`, `ExactSizeIterator`, and - `FusedIterator`, by @cuviper in PR [196]. +- More of the iterator types implement `Debug`, `ExactSizeIterator`, and + `FusedIterator`, by @cuviper in PR [196]. - - `IndexMap` and `IndexSet` now implement rayon's `ParallelDrainRange`, - by @cuviper in PR [197]. +- `IndexMap` and `IndexSet` now implement rayon's `ParallelDrainRange`, + by @cuviper in PR [197]. - - `IndexMap::with_hasher` and `IndexSet::with_hasher` are now `const` - functions, allowing static maps and sets, by @mwillsey in PR [203]. +- `IndexMap::with_hasher` and `IndexSet::with_hasher` are now `const` + functions, allowing static maps and sets, by @mwillsey in PR [203]. - - `IndexMap` and `IndexSet` now implement `From` for arrays, matching - Rust 1.56's implementation for `HashMap`, by @rouge8 in PR [205]. +- `IndexMap` and `IndexSet` now implement `From` for arrays, matching + Rust 1.56's implementation for `HashMap`, by @rouge8 in PR [205]. - - `IndexMap` and `IndexSet` now have methods `sort_unstable_keys`, - `sort_unstable_by`, `sorted_unstable_by`, and `par_*` equivalents, - which sort in-place without preserving the order of equal items, by - @bhgomes in PR [211]. +- `IndexMap` and `IndexSet` now have methods `sort_unstable_keys`, + `sort_unstable_by`, `sorted_unstable_by`, and `par_*` equivalents, + which sort in-place without preserving the order of equal items, by + @bhgomes in PR [211]. [195]: https://github.com/indexmap-rs/indexmap/pull/195 [196]: https://github.com/indexmap-rs/indexmap/pull/196 @@ -162,320 +164,320 @@ [205]: https://github.com/indexmap-rs/indexmap/pull/205 [211]: https://github.com/indexmap-rs/indexmap/pull/211 -- 1.7.0 +## 1.7.0 - - **MSRV**: Rust 1.49 or later is now required. +- **MSRV**: Rust 1.49 or later is now required. - - The `hashbrown` dependency has been updated to version 0.11. +- The `hashbrown` dependency has been updated to version 0.11. -- 1.6.2 +## 1.6.2 - - Fixed to match `std` behavior, `OccupiedEntry::key` now references the - existing key in the map instead of the lookup key, by @cuviper in PR [170]. +- Fixed to match `std` behavior, `OccupiedEntry::key` now references the + existing key in the map instead of the lookup key, by @cuviper in PR [170]. - - The new `Entry::or_insert_with_key` matches Rust 1.50's `Entry` method, - passing `&K` to the callback to create a value, by @cuviper in PR [175]. +- The new `Entry::or_insert_with_key` matches Rust 1.50's `Entry` method, + passing `&K` to the callback to create a value, by @cuviper in PR [175]. [170]: https://github.com/indexmap-rs/indexmap/pull/170 [175]: https://github.com/indexmap-rs/indexmap/pull/175 -- 1.6.1 +## 1.6.1 - - The new `serde_seq` module implements `IndexMap` serialization as a - sequence to ensure order is preserved, by @cuviper in PR [158]. +- The new `serde_seq` module implements `IndexMap` serialization as a + sequence to ensure order is preserved, by @cuviper in PR [158]. - - New methods on maps and sets work like the `Vec`/slice methods by the same name: - `truncate`, `split_off`, `first`, `first_mut`, `last`, `last_mut`, and - `swap_indices`, by @cuviper in PR [160]. +- New methods on maps and sets work like the `Vec`/slice methods by the same name: + `truncate`, `split_off`, `first`, `first_mut`, `last`, `last_mut`, and + `swap_indices`, by @cuviper in PR [160]. [158]: https://github.com/indexmap-rs/indexmap/pull/158 [160]: https://github.com/indexmap-rs/indexmap/pull/160 -- 1.6.0 +## 1.6.0 - - **MSRV**: Rust 1.36 or later is now required. +- **MSRV**: Rust 1.36 or later is now required. - - The `hashbrown` dependency has been updated to version 0.9. +- The `hashbrown` dependency has been updated to version 0.9. -- 1.5.2 +## 1.5.2 - - The new "std" feature will force the use of `std` for users that explicitly - want the default `S = RandomState`, bypassing the autodetection added in 1.3.0, - by @cuviper in PR [145]. +- The new "std" feature will force the use of `std` for users that explicitly + want the default `S = RandomState`, bypassing the autodetection added in 1.3.0, + by @cuviper in PR [145]. [145]: https://github.com/indexmap-rs/indexmap/pull/145 -- 1.5.1 +## 1.5.1 - - Values can now be indexed by their `usize` position by @cuviper in PR [132]. +- Values can now be indexed by their `usize` position by @cuviper in PR [132]. - - Some of the generic bounds have been relaxed to match `std` by @cuviper in PR [141]. +- Some of the generic bounds have been relaxed to match `std` by @cuviper in PR [141]. - - `drain` now accepts any `R: RangeBounds` by @cuviper in PR [142]. +- `drain` now accepts any `R: RangeBounds` by @cuviper in PR [142]. [132]: https://github.com/indexmap-rs/indexmap/pull/132 [141]: https://github.com/indexmap-rs/indexmap/pull/141 [142]: https://github.com/indexmap-rs/indexmap/pull/142 -- 1.5.0 +## 1.5.0 - - **MSRV**: Rust 1.32 or later is now required. +- **MSRV**: Rust 1.32 or later is now required. - - The inner hash table is now based on `hashbrown` by @cuviper in PR [131]. - This also completes the method `reserve` and adds `shrink_to_fit`. +- The inner hash table is now based on `hashbrown` by @cuviper in PR [131]. + This also completes the method `reserve` and adds `shrink_to_fit`. - - Add new methods `get_key_value`, `remove_entry`, `swap_remove_entry`, - and `shift_remove_entry`, by @cuviper in PR [136] +- Add new methods `get_key_value`, `remove_entry`, `swap_remove_entry`, + and `shift_remove_entry`, by @cuviper in PR [136] - - `Clone::clone_from` reuses allocations by @cuviper in PR [125] +- `Clone::clone_from` reuses allocations by @cuviper in PR [125] - - Add new method `reverse` by @linclelinkpart5 in PR [128] +- Add new method `reverse` by @linclelinkpart5 in PR [128] [125]: https://github.com/indexmap-rs/indexmap/pull/125 [128]: https://github.com/indexmap-rs/indexmap/pull/128 [131]: https://github.com/indexmap-rs/indexmap/pull/131 [136]: https://github.com/indexmap-rs/indexmap/pull/136 -- 1.4.0 +## 1.4.0 - - Add new method `get_index_of` by @Thermatrix in PR [115] and [120] +- Add new method `get_index_of` by @Thermatrix in PR [115] and [120] - - Fix build script rebuild-if-changed configuration to use "build.rs"; - fixes issue [123]. Fix by @cuviper. +- Fix build script rebuild-if-changed configuration to use "build.rs"; + fixes issue [123]. Fix by @cuviper. - - Dev-dependencies (rand and quickcheck) have been updated. The crate's tests - now run using Rust 1.32 or later (MSRV for building the crate has not changed). - by @kjeremy and @bluss +- Dev-dependencies (rand and quickcheck) have been updated. The crate's tests + now run using Rust 1.32 or later (MSRV for building the crate has not changed). + by @kjeremy and @bluss [123]: https://github.com/indexmap-rs/indexmap/issues/123 [115]: https://github.com/indexmap-rs/indexmap/pull/115 [120]: https://github.com/indexmap-rs/indexmap/pull/120 -- 1.3.2 +## 1.3.2 - - Maintenance update to regenerate the published `Cargo.toml`. +- Maintenance update to regenerate the published `Cargo.toml`. -- 1.3.1 +## 1.3.1 - - Maintenance update for formatting and `autocfg` 1.0. +- Maintenance update for formatting and `autocfg` 1.0. -- 1.3.0 +## 1.3.0 - - The deprecation messages in the previous version have been removed. - (The methods have not otherwise changed.) Docs for removal methods have been - improved. - - From Rust 1.36, this crate supports being built **without std**, requiring - `alloc` instead. This is enabled automatically when it is detected that - `std` is not available. There is no crate feature to enable/disable to - trigger this. The new build-dep `autocfg` enables this. +- The deprecation messages in the previous version have been removed. + (The methods have not otherwise changed.) Docs for removal methods have been + improved. +- From Rust 1.36, this crate supports being built **without std**, requiring + `alloc` instead. This is enabled automatically when it is detected that + `std` is not available. There is no crate feature to enable/disable to + trigger this. The new build-dep `autocfg` enables this. -- 1.2.0 +## 1.2.0 - - Plain `.remove()` now has a deprecation message, it informs the user - about picking one of the removal functions `swap_remove` and `shift_remove` - which have different performance and order semantics. - Plain `.remove()` will not be removed, the warning message and method - will remain until further. +- Plain `.remove()` now has a deprecation message, it informs the user + about picking one of the removal functions `swap_remove` and `shift_remove` + which have different performance and order semantics. + Plain `.remove()` will not be removed, the warning message and method + will remain until further. - - Add new method `shift_remove` for order preserving removal on the map, - and `shift_take` for the corresponding operation on the set. +- Add new method `shift_remove` for order preserving removal on the map, + and `shift_take` for the corresponding operation on the set. - - Add methods `swap_remove`, `swap_remove_entry` to `Entry`. +- Add methods `swap_remove`, `swap_remove_entry` to `Entry`. - - Fix indexset/indexmap to support full paths, like `indexmap::indexmap!()` +- Fix indexset/indexmap to support full paths, like `indexmap::indexmap!()` - - Internal improvements: fix warnings, deprecations and style lints +- Internal improvements: fix warnings, deprecations and style lints -- 1.1.0 +## 1.1.0 - - Added optional feature `"rayon"` that adds parallel iterator support - to `IndexMap` and `IndexSet` using Rayon. This includes all the regular - iterators in parallel versions, and parallel sort. +- Added optional feature `"rayon"` that adds parallel iterator support + to `IndexMap` and `IndexSet` using Rayon. This includes all the regular + iterators in parallel versions, and parallel sort. - - Implemented `Clone` for `map::{Iter, Keys, Values}` and - `set::{Difference, Intersection, Iter, SymmetricDifference, Union}` +- Implemented `Clone` for `map::{Iter, Keys, Values}` and + `set::{Difference, Intersection, Iter, SymmetricDifference, Union}` - - Implemented `Debug` for `map::{Entry, IntoIter, Iter, Keys, Values}` and - `set::{Difference, Intersection, IntoIter, Iter, SymmetricDifference, Union}` +- Implemented `Debug` for `map::{Entry, IntoIter, Iter, Keys, Values}` and + `set::{Difference, Intersection, IntoIter, Iter, SymmetricDifference, Union}` - - Serde trait `IntoDeserializer` are implemented for `IndexMap` and `IndexSet`. +- Serde trait `IntoDeserializer` are implemented for `IndexMap` and `IndexSet`. - - Minimum Rust version requirement increased to Rust 1.30 for development builds. +- Minimum Rust version requirement increased to Rust 1.30 for development builds. -- 1.0.2 +## 1.0.2 - - The new methods `IndexMap::insert_full` and `IndexSet::insert_full` are - both like `insert` with the index included in the return value. +- The new methods `IndexMap::insert_full` and `IndexSet::insert_full` are + both like `insert` with the index included in the return value. - - The new method `Entry::and_modify` can be used to modify occupied - entries, matching the new methods of `std` maps in Rust 1.26. +- The new method `Entry::and_modify` can be used to modify occupied + entries, matching the new methods of `std` maps in Rust 1.26. - - The new method `Entry::or_default` inserts a default value in unoccupied - entries, matching the new methods of `std` maps in Rust 1.28. +- The new method `Entry::or_default` inserts a default value in unoccupied + entries, matching the new methods of `std` maps in Rust 1.28. -- 1.0.1 +## 1.0.1 - - Document Rust version policy for the crate (see rustdoc) +- Document Rust version policy for the crate (see rustdoc) -- 1.0.0 +## 1.0.0 - - This is the 1.0 release for `indexmap`! (the crate and datastructure - formerly known as “ordermap”) - - `OccupiedEntry::insert` changed its signature, to use `&mut self` for - the method receiver, matching the equivalent method for a standard - `HashMap`. Thanks to @dtolnay for finding this bug. - - The deprecated old names from ordermap were removed: `OrderMap`, - `OrderSet`, `ordermap!{}`, `orderset!{}`. Use the new `IndexMap` - etc names instead. +- This is the 1.0 release for `indexmap`! (the crate and datastructure + formerly known as “ordermap”) +- `OccupiedEntry::insert` changed its signature, to use `&mut self` for + the method receiver, matching the equivalent method for a standard + `HashMap`. Thanks to @dtolnay for finding this bug. +- The deprecated old names from ordermap were removed: `OrderMap`, + `OrderSet`, `ordermap!{}`, `orderset!{}`. Use the new `IndexMap` + etc names instead. -- 0.4.1 +## 0.4.1 - - Renamed crate to `indexmap`; the `ordermap` crate is now deprecated - and the types `OrderMap/Set` now have a deprecation notice. +- Renamed crate to `indexmap`; the `ordermap` crate is now deprecated + and the types `OrderMap/Set` now have a deprecation notice. -- 0.4.0 +## 0.4.0 - - This is the last release series for this `ordermap` under that name, - because the crate is **going to be renamed** to `indexmap` (with types - `IndexMap`, `IndexSet`) and no change in functionality! - - The map and its associated structs moved into the `map` submodule of the - crate, so that the map and set are symmetric +- This is the last release series for this `ordermap` under that name, + because the crate is **going to be renamed** to `indexmap` (with types + `IndexMap`, `IndexSet`) and no change in functionality! +- The map and its associated structs moved into the `map` submodule of the + crate, so that the map and set are symmetric + The iterators, `Entry` and other structs are now under `ordermap::map::` - - Internally refactored `OrderMap` so that all the main algorithms - (insertion, lookup, removal etc) that don't use the `S` parameter (the - hasher) are compiled without depending on `S`, which reduces generics bloat. +- Internally refactored `OrderMap` so that all the main algorithms + (insertion, lookup, removal etc) that don't use the `S` parameter (the + hasher) are compiled without depending on `S`, which reduces generics bloat. - - `Entry` no longer has a type parameter `S`, which is just like - the standard `HashMap`'s entry. +- `Entry` no longer has a type parameter `S`, which is just like + the standard `HashMap`'s entry. - - Minimum Rust version requirement increased to Rust 1.18 +- Minimum Rust version requirement increased to Rust 1.18 -- 0.3.5 +## 0.3.5 - - Documentation improvements +- Documentation improvements -- 0.3.4 +## 0.3.4 - - The `.retain()` methods for `OrderMap` and `OrderSet` now - traverse the elements in order, and the retained elements **keep their order** - - Added new methods `.sort_by()`, `.sort_keys()` to `OrderMap` and - `.sort_by()`, `.sort()` to `OrderSet`. These methods allow you to - sort the maps in place efficiently. +- The `.retain()` methods for `OrderMap` and `OrderSet` now + traverse the elements in order, and the retained elements **keep their order** +- Added new methods `.sort_by()`, `.sort_keys()` to `OrderMap` and + `.sort_by()`, `.sort()` to `OrderSet`. These methods allow you to + sort the maps in place efficiently. -- 0.3.3 +## 0.3.3 - - Document insertion behaviour better by @lucab - - Updated dependences (no feature changes) by @ignatenkobrain +- Document insertion behaviour better by @lucab +- Updated dependences (no feature changes) by @ignatenkobrain -- 0.3.2 +## 0.3.2 - - Add `OrderSet` by @cuviper! - - `OrderMap::drain` is now (too) a double ended iterator. +- Add `OrderSet` by @cuviper! +- `OrderMap::drain` is now (too) a double ended iterator. -- 0.3.1 +## 0.3.1 - - In all ordermap iterators, forward the `collect` method to the underlying - iterator as well. - - Add crates.io categories. +- In all ordermap iterators, forward the `collect` method to the underlying + iterator as well. +- Add crates.io categories. -- 0.3.0 +## 0.3.0 - - The methods `get_pair`, `get_pair_index` were both replaced by - `get_full` (and the same for the mutable case). - - Method `swap_remove_pair` replaced by `swap_remove_full`. - - Add trait `MutableKeys` for opt-in mutable key access. Mutable key access - is only possible through the methods of this extension trait. - - Add new trait `Equivalent` for key equivalence. This extends the - `Borrow` trait mechanism for `OrderMap::get` in a backwards compatible - way, just some minor type inference related issues may become apparent. - See [#10] for more information. - - Implement `Extend<(&K, &V)>` by @xfix. +- The methods `get_pair`, `get_pair_index` were both replaced by + `get_full` (and the same for the mutable case). +- Method `swap_remove_pair` replaced by `swap_remove_full`. +- Add trait `MutableKeys` for opt-in mutable key access. Mutable key access + is only possible through the methods of this extension trait. +- Add new trait `Equivalent` for key equivalence. This extends the + `Borrow` trait mechanism for `OrderMap::get` in a backwards compatible + way, just some minor type inference related issues may become apparent. + See [#10] for more information. +- Implement `Extend<(&K, &V)>` by @xfix. [#10]: https://github.com/indexmap-rs/indexmap/pull/10 -- 0.2.13 +## 0.2.13 - - Fix deserialization to support custom hashers by @Techcable. - - Add methods `.index()` on the entry types by @garro95. +- Fix deserialization to support custom hashers by @Techcable. +- Add methods `.index()` on the entry types by @garro95. -- 0.2.12 +## 0.2.12 - - Add methods `.with_hasher()`, `.hasher()`. +- Add methods `.with_hasher()`, `.hasher()`. -- 0.2.11 +## 0.2.11 - - Support `ExactSizeIterator` for the iterators. By @Binero. - - Use `Box<[Pos]>` internally, saving a word in the `OrderMap` struct. - - Serde support, with crate feature `"serde-1"`. By @xfix. +- Support `ExactSizeIterator` for the iterators. By @Binero. +- Use `Box<[Pos]>` internally, saving a word in the `OrderMap` struct. +- Serde support, with crate feature `"serde-1"`. By @xfix. -- 0.2.10 +## 0.2.10 - - Add iterator `.drain(..)` by @stevej. +- Add iterator `.drain(..)` by @stevej. -- 0.2.9 +## 0.2.9 - - Add method `.is_empty()` by @overvenus. - - Implement `PartialEq, Eq` by @overvenus. - - Add method `.sorted_by()`. +- Add method `.is_empty()` by @overvenus. +- Implement `PartialEq, Eq` by @overvenus. +- Add method `.sorted_by()`. -- 0.2.8 +## 0.2.8 - - Add iterators `.values()` and `.values_mut()`. - - Fix compatibility with 32-bit platforms. +- Add iterators `.values()` and `.values_mut()`. +- Fix compatibility with 32-bit platforms. -- 0.2.7 +## 0.2.7 - - Add `.retain()`. +- Add `.retain()`. -- 0.2.6 +## 0.2.6 - - Add `OccupiedEntry::remove_entry` and other minor entry methods, - so that it now has all the features of `HashMap`'s entries. +- Add `OccupiedEntry::remove_entry` and other minor entry methods, + so that it now has all the features of `HashMap`'s entries. -- 0.2.5 +## 0.2.5 - - Improved `.pop()` slightly. +- Improved `.pop()` slightly. -- 0.2.4 +## 0.2.4 - - Improved performance of `.insert()` ([#3]) by @pczarn. +- Improved performance of `.insert()` ([#3]) by @pczarn. [#3]: https://github.com/indexmap-rs/indexmap/pull/3 -- 0.2.3 +## 0.2.3 - - Generalize `Entry` for now, so that it works on hashmaps with non-default - hasher. However, there's a lingering compat issue since libstd `HashMap` - does not parameterize its entries by the hasher (`S` typarm). - - Special case some iterator methods like `.nth()`. +- Generalize `Entry` for now, so that it works on hashmaps with non-default + hasher. However, there's a lingering compat issue since libstd `HashMap` + does not parameterize its entries by the hasher (`S` typarm). +- Special case some iterator methods like `.nth()`. -- 0.2.2 +## 0.2.2 - - Disable the verbose `Debug` impl by default. +- Disable the verbose `Debug` impl by default. -- 0.2.1 +## 0.2.1 - - Fix doc links and clarify docs. +- Fix doc links and clarify docs. -- 0.2.0 +## 0.2.0 - - Add more `HashMap` methods & compat with its API. - - Experimental support for `.entry()` (the simplest parts of the API). - - Add `.reserve()` (placeholder impl). - - Add `.remove()` as synonym for `.swap_remove()`. - - Changed `.insert()` to swap value if the entry already exists, and - return `Option`. - - Experimental support as an *indexed* hash map! Added methods - `.get_index()`, `.get_index_mut()`, `.swap_remove_index()`, - `.get_pair_index()`, `.get_pair_index_mut()`. +- Add more `HashMap` methods & compat with its API. +- Experimental support for `.entry()` (the simplest parts of the API). +- Add `.reserve()` (placeholder impl). +- Add `.remove()` as synonym for `.swap_remove()`. +- Changed `.insert()` to swap value if the entry already exists, and + return `Option`. +- Experimental support as an *indexed* hash map! Added methods + `.get_index()`, `.get_index_mut()`, `.swap_remove_index()`, + `.get_pair_index()`, `.get_pair_index_mut()`. -- 0.1.2 +## 0.1.2 - - Implement the 32/32 split idea for `Pos` which improves cache utilization - and lookup performance. +- Implement the 32/32 split idea for `Pos` which improves cache utilization + and lookup performance. -- 0.1.1 +## 0.1.1 - - Initial release. +- Initial release. From 76b423221e5c07ef29f10947c150f7d1a9f8c180 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 1 Feb 2024 11:32:49 -0800 Subject: [PATCH 167/236] Make the CI status more robust --- .github/workflows/ci.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 592b8337..0f7413ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -112,9 +112,16 @@ jobs: - name: Build run: cargo build --verbose --all-features - done: + ci-success: name: Complete runs-on: ubuntu-latest needs: [tests, nostd_build, clippy, miri, minimal-versions] steps: - run: exit 0 + ci-failed: + name: Complete + runs-on: ubuntu-latest + needs: [tests, nostd_build, clippy, miri, minimal-versions] + if: failure() + steps: + - run: exit 1 From 12e3161455c59a00dcc963b78d6215eb20dafb40 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 6 Feb 2024 10:45:08 -0800 Subject: [PATCH 168/236] ci: Use a single action to collect final status This is copied from how rust-lang/miri-test-libstd does it. --- .github/workflows/ci.yml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f7413ea..ff65732d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -112,16 +112,17 @@ jobs: - name: Build run: cargo build --verbose --all-features - ci-success: - name: Complete + # One job that "summarizes" the success state of this pipeline. This can then be added to branch + # protection, rather than having to add each job separately. + success: + name: Success runs-on: ubuntu-latest needs: [tests, nostd_build, clippy, miri, minimal-versions] + # Github branch protection is exceedingly silly and treats "jobs skipped because a dependency + # failed" as success. So we have to do some contortions to ensure the job fails if any of its + # dependencies fails. + if: always() # make sure this is never "skipped" steps: - - run: exit 0 - ci-failed: - name: Complete - runs-on: ubuntu-latest - needs: [tests, nostd_build, clippy, miri, minimal-versions] - if: failure() - steps: - - run: exit 1 + # Manually check the status of all dependencies. `if: failure()` does not work. + - name: check if any dependency failed + run: jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' From c7875ef925b0aabfebf9e8408f3cd1faf5d64e24 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 6 Feb 2024 10:48:10 -0800 Subject: [PATCH 169/236] ci: Simplify toolchain in minimal-versions --- .github/workflows/ci.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff65732d..28cf5c4a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,12 +99,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: nightly - - uses: dtolnay/rust-toolchain@master - with: - toolchain: 1.63.0 # MSRV + - uses: dtolnay/rust-toolchain@nightly + - uses: dtolnay/rust-toolchain@1.63.0 # MSRV - uses: taiki-e/install-action@v2 with: tool: cargo-hack From 2d8fdb8dc73409b15e73ffed80bc54735edfb879 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 6 Feb 2024 10:51:01 -0800 Subject: [PATCH 170/236] ci: Add a registry cache for git protocol --- .github/workflows/ci.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 28cf5c4a..a07f9b4f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,11 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: actions/cache@v4 + if: matrix.rust == '1.63.0' + with: + path: ~/.cargo/registry/index + key: cargo-git-index - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} @@ -67,6 +72,11 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: actions/cache@v4 + if: matrix.rust == '1.63.0' + with: + path: ~/.cargo/registry/index + key: cargo-git-index - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} @@ -99,6 +109,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: actions/cache@v4 + with: + path: ~/.cargo/registry/index + key: cargo-git-index - uses: dtolnay/rust-toolchain@nightly - uses: dtolnay/rust-toolchain@1.63.0 # MSRV - uses: taiki-e/install-action@v2 From 51907787ee69bfaf54aee393d488276ad4461125 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sat, 10 Feb 2024 10:42:23 -0800 Subject: [PATCH 171/236] 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 172/236] 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 173/236] 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 174/236] 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 175/236] 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 176/236] 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 177/236] 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 178/236] 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] From 2a33977a155826904eaecef251f91ac5c566f359 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sun, 11 Feb 2024 13:35:53 -0800 Subject: [PATCH 179/236] Fully mask the `"test_debug"` code from normal builds ... but also add `--all-features` to CI so we're sure it builds. --- .github/workflows/ci.yml | 2 +- src/map.rs | 18 ++++++++++-------- src/map/core.rs | 10 +++++----- src/map/core/raw.rs | 8 +++++--- src/set.rs | 14 ++++++++------ 5 files changed, 29 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a07f9b4f..7631a57b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,7 +93,7 @@ jobs: - uses: dtolnay/rust-toolchain@beta with: components: clippy - - run: cargo clippy + - run: cargo clippy --all-features miri: runs-on: ubuntu-latest diff --git a/src/map.rs b/src/map.rs index 428fac40..5fe0bb18 100644 --- a/src/map.rs +++ b/src/map.rs @@ -141,15 +141,17 @@ where K: fmt::Debug, V: fmt::Debug, { + #[cfg(not(feature = "test_debug"))] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if cfg!(not(feature = "test_debug")) { - f.debug_map().entries(self.iter()).finish() - } else { - // Let the inner `IndexMapCore` print all of its details - f.debug_struct("IndexMap") - .field("core", &self.core) - .finish() - } + f.debug_map().entries(self.iter()).finish() + } + + #[cfg(feature = "test_debug")] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Let the inner `IndexMapCore` print all of its details + f.debug_struct("IndexMap") + .field("core", &self.core) + .finish() } } diff --git a/src/map/core.rs b/src/map/core.rs index 2dca04a5..853c86ed 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -16,7 +16,6 @@ use hashbrown::raw::RawTable; use crate::vec::{self, Vec}; use crate::TryReserveError; -use core::fmt; use core::mem; use core::ops::RangeBounds; @@ -83,12 +82,13 @@ where } } -impl fmt::Debug for IndexMapCore +#[cfg(feature = "test_debug")] +impl core::fmt::Debug for IndexMapCore where - K: fmt::Debug, - V: fmt::Debug, + K: core::fmt::Debug, + V: core::fmt::Debug, { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("IndexMapCore") .field("indices", &raw::DebugIndices(&self.indices)) .field("entries", &self.entries) diff --git a/src/map/core/raw.rs b/src/map/core/raw.rs index 233e41e7..10bbea53 100644 --- a/src/map/core/raw.rs +++ b/src/map/core/raw.rs @@ -3,7 +3,6 @@ //! mostly in dealing with its bucket "pointers". use super::{equivalent, get_hash, Bucket, HashValue, IndexMapCore}; -use core::fmt; use hashbrown::raw::RawTable; type RawBucket = hashbrown::raw::Bucket; @@ -21,9 +20,12 @@ pub(super) fn insert_bulk_no_grow(indices: &mut RawTable, entries: } } +#[cfg(feature = "test_debug")] pub(super) struct DebugIndices<'a>(pub &'a RawTable); -impl fmt::Debug for DebugIndices<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + +#[cfg(feature = "test_debug")] +impl core::fmt::Debug for DebugIndices<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { // SAFETY: we're not letting any of the buckets escape this function let indices = unsafe { self.0.iter().map(|raw_bucket| *raw_bucket.as_ref()) }; f.debug_list().entries(indices).finish() diff --git a/src/set.rs b/src/set.rs index e2560843..ed6bf35a 100644 --- a/src/set.rs +++ b/src/set.rs @@ -133,13 +133,15 @@ impl fmt::Debug for IndexSet where T: fmt::Debug, { + #[cfg(not(feature = "test_debug"))] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if cfg!(not(feature = "test_debug")) { - f.debug_set().entries(self.iter()).finish() - } else { - // Let the inner `IndexMap` print all of its details - f.debug_struct("IndexSet").field("map", &self.map).finish() - } + f.debug_set().entries(self.iter()).finish() + } + + #[cfg(feature = "test_debug")] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Let the inner `IndexMap` print all of its details + f.debug_struct("IndexSet").field("map", &self.map).finish() } } From 3b79b87ac1e72a796b6fd63b57f6eaf2fdd383f6 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sun, 11 Feb 2024 14:44:14 -0800 Subject: [PATCH 180/236] Release 2.2.3 --- Cargo.toml | 2 +- RELEASES.md | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 022a2588..60bda34b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "indexmap" edition = "2021" -version = "2.2.2" +version = "2.2.3" 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 589a93a7..05d53af6 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,16 @@ # Releases +## 2.2.3 + +- Added `move_index` and `swap_indices` methods to `IndexedEntry`, + `OccupiedEntry`, and `RawOccupiedEntryMut`, functioning like the existing + methods on `IndexMap`. +- Added `shift_insert` methods on `VacantEntry` and `RawVacantEntryMut`, as + well as `shift_insert_hashed_nocheck` on the latter, to insert the new entry + at a particular index. +- Added `shift_insert` methods on `IndexMap` and `IndexSet` to insert a new + entry at a particular index, or else move an existing entry there. + ## 2.2.2 - Added indexing methods to raw entries: `RawEntryBuilder::from_hash_full`, From 8b982534fe0b018bac9d211196200a61a640eb4e Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sun, 25 Feb 2024 12:52:43 -0800 Subject: [PATCH 181/236] Avoid hashing for single-entry maps We already avoid hashing for get/remove on empty maps, but we can also do this for single-entry maps. Even with a cheap hash function, a single equality check ought to be faster than any hash table lookup. --- src/map.rs | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/map.rs b/src/map.rs index 676b31aa..ffb60019 100644 --- a/src/map.rs +++ b/src/map.rs @@ -563,11 +563,13 @@ where where Q: Hash + Equivalent, { - if self.is_empty() { - None - } else { - let hash = self.hash(key); - self.core.get_index_of(hash, key) + match self.as_entries() { + [] => None, + [x] => key.equivalent(&x.key).then_some(0), + _ => { + let hash = self.hash(key); + self.core.get_index_of(hash, key) + } } } @@ -676,11 +678,17 @@ where where Q: Hash + Equivalent, { - if self.is_empty() { - return None; + match self.as_entries() { + [x] if key.equivalent(&x.key) => { + let (k, v) = self.core.pop()?; + Some((0, k, v)) + } + [_] | [] => None, + _ => { + let hash = self.hash(key); + self.core.swap_remove_full(hash, key) + } } - let hash = self.hash(key); - self.core.swap_remove_full(hash, key) } /// Remove the key-value pair equivalent to `key` and return @@ -733,11 +741,17 @@ where where Q: Hash + Equivalent, { - if self.is_empty() { - return None; + match self.as_entries() { + [x] if key.equivalent(&x.key) => { + let (k, v) = self.core.pop()?; + Some((0, k, v)) + } + [_] | [] => None, + _ => { + let hash = self.hash(key); + self.core.shift_remove_full(hash, key) + } } - let hash = self.hash(key); - self.core.shift_remove_full(hash, key) } } From 1d7b8e27fde9634915e15159de17c7d813a05e0c Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sat, 24 Feb 2024 15:56:15 -0800 Subject: [PATCH 182/236] Add `insert_sorted` --- src/map.rs | 33 +++++++++++++++++++++++++++++++++ src/map/core/entry.rs | 18 ++++++++++++++++++ src/set.rs | 30 ++++++++++++++++++++++++++++++ tests/quick.rs | 17 +++++++++++++++++ 4 files changed, 98 insertions(+) diff --git a/src/map.rs b/src/map.rs index ffb60019..46965a7c 100644 --- a/src/map.rs +++ b/src/map.rs @@ -414,6 +414,35 @@ where self.core.insert_full(hash, key, value) } + /// Insert a key-value pair in the map at its ordered position among sorted keys. + /// + /// 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. + /// + /// If the sorted key is found in the map, its corresponding value is + /// updated with `value`, and the older value is returned inside + /// `(index, Some(_))`. Otherwise, the new key-value pair is inserted at + /// the sorted position, and `(index, None)` is returned. + /// + /// If the existing keys are **not** already sorted, then the insertion + /// index is unspecified (like [`slice::binary_search`]), but the key-value + /// pair is moved to or inserted at that position regardless. + /// + /// Computes in **O(n)** time (average). Instead of repeating calls to + /// `insert_sorted`, it may be faster to call batched [`insert`][Self::insert] + /// or [`extend`][Self::extend] and only call [`sort_keys`][Self::sort_keys] + /// or [`sort_unstable_keys`][Self::sort_unstable_keys] once. + pub fn insert_sorted(&mut self, key: K, value: V) -> (usize, Option) + where + K: Ord, + { + 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)), + } + } + /// 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 @@ -788,6 +817,10 @@ impl IndexMap { /// Sort the map’s key-value pairs by the default ordering of the keys. /// + /// This is a stable sort -- but equivalent keys should not normally coexist in + /// a map at all, so [`sort_unstable_keys`][Self::sort_unstable_keys] is preferred + /// because it is generally faster and doesn't allocate auxiliary memory. + /// /// See [`sort_by`](Self::sort_by) for details. pub fn sort_keys(&mut self) where diff --git a/src/map/core/entry.rs b/src/map/core/entry.rs index 48c00294..6c310707 100644 --- a/src/map/core/entry.rs +++ b/src/map/core/entry.rs @@ -310,6 +310,24 @@ impl<'a, K, V> VacantEntry<'a, K, V> { &mut map.entries[i].value } + /// Inserts the entry's key and the given value into the map at its ordered + /// position among sorted keys, and returns the new index and a mutable + /// reference to the value. + /// + /// If the existing keys are **not** already sorted, then the insertion + /// index is unspecified (like [`slice::binary_search`]), but the key-value + /// pair is inserted at that position regardless. + /// + /// Computes in **O(n)** time (average). + pub fn insert_sorted(self, value: V) -> (usize, &'a mut V) + where + K: Ord, + { + let slice = crate::map::Slice::from_slice(&self.map.entries); + let i = slice.binary_search_keys(&self.key).unwrap_err(); + (i, self.shift_insert(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. /// diff --git a/src/set.rs b/src/set.rs index 0c0358cd..5774349d 100644 --- a/src/set.rs +++ b/src/set.rs @@ -355,6 +355,32 @@ where (index, existing.is_none()) } + /// Insert the value into the set at its ordered position among sorted values. + /// + /// 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. + /// + /// 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 + /// new item and returns its sorted index and `true`. + /// + /// If the existing items are **not** already sorted, then the insertion + /// index is unspecified (like [`slice::binary_search`]), but the value + /// is moved to or inserted at that position regardless. + /// + /// Computes in **O(n)** time (average). Instead of repeating calls to + /// `insert_sorted`, it may be faster to call batched [`insert`][Self::insert] + /// or [`extend`][Self::extend] and only call [`sort`][Self::sort] or + /// [`sort_unstable`][Self::sort_unstable] once. + pub fn insert_sorted(&mut self, value: T) -> (usize, bool) + where + T: Ord, + { + let (index, existing) = self.map.insert_sorted(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 @@ -670,6 +696,10 @@ impl IndexSet { /// Sort the set’s values by their default ordering. /// + /// This is a stable sort -- but equivalent values should not normally coexist in + /// a set at all, so [`sort_unstable`][Self::sort_unstable] is preferred + /// because it is generally faster and doesn't allocate auxiliary memory. + /// /// See [`sort_by`](Self::sort_by) for details. pub fn sort(&mut self) where diff --git a/tests/quick.rs b/tests/quick.rs index c32d0b91..56afee72 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -113,6 +113,23 @@ quickcheck_limit! { true } + fn insert_sorted(insert: Vec<(u32, u32)>) -> bool { + let mut hmap = HashMap::new(); + let mut map = IndexMap::new(); + let mut map2 = IndexMap::new(); + for &(key, value) in &insert { + hmap.insert(key, value); + map.insert_sorted(key, value); + match map2.entry(key) { + Entry::Occupied(e) => *e.into_mut() = value, + Entry::Vacant(e) => { e.insert_sorted(value); } + } + } + itertools::assert_equal(hmap.iter().sorted(), &map); + itertools::assert_equal(&map, &map2); + true + } + fn pop(insert: Vec) -> bool { let mut map = IndexMap::new(); for &key in &insert { From fac3148c79fa4a0a84cd0046d78a53004ddc27d1 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Mon, 26 Feb 2024 15:41:26 -0800 Subject: [PATCH 183/236] Limit preallocation in deserializers --- src/map/serde_seq.rs | 3 ++- src/serde.rs | 35 +++++++++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/map/serde_seq.rs b/src/map/serde_seq.rs index c35a234d..602ae7dc 100644 --- a/src/map/serde_seq.rs +++ b/src/map/serde_seq.rs @@ -26,6 +26,7 @@ use core::hash::{BuildHasher, Hash}; use core::marker::PhantomData; use crate::map::Slice as MapSlice; +use crate::serde::cautious_capacity; use crate::set::Slice as SetSlice; use crate::IndexMap; @@ -101,7 +102,7 @@ where where A: SeqAccess<'de>, { - let capacity = seq.size_hint().unwrap_or(0); + let capacity = cautious_capacity::(seq.size_hint()); let mut map = IndexMap::with_capacity_and_hasher(capacity, S::default()); while let Some((key, value)) = seq.next_element()? { diff --git a/src/serde.rs b/src/serde.rs index 8920a0bd..44760145 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -9,8 +9,29 @@ use serde::ser::{Serialize, Serializer}; use core::fmt::{self, Formatter}; use core::hash::{BuildHasher, Hash}; use core::marker::PhantomData; - -use crate::IndexMap; +use core::{cmp, mem}; + +use crate::{Bucket, IndexMap, IndexSet}; + +/// Limit our preallocated capacity from a deserializer `size_hint()`. +/// +/// We do account for the `Bucket` overhead from its saved `hash` field, but we don't count the +/// `RawTable` allocation or the fact that its raw capacity will be rounded up to a power of two. +/// The "max" is an arbitrary choice anyway, not something that needs precise adherence. +/// +/// This is based on the internal `serde::de::size_hint::cautious(hint)` function. +pub(crate) fn cautious_capacity(hint: Option) -> usize { + const MAX_PREALLOC_BYTES: usize = 1024 * 1024; + + if mem::size_of::>() == 0 { + 0 + } else { + cmp::min( + hint.unwrap_or(0), + MAX_PREALLOC_BYTES / mem::size_of::>(), + ) + } +} impl Serialize for IndexMap where @@ -43,8 +64,8 @@ where where A: MapAccess<'de>, { - let mut values = - IndexMap::with_capacity_and_hasher(map.size_hint().unwrap_or(0), S::default()); + let capacity = cautious_capacity::(map.size_hint()); + let mut values = IndexMap::with_capacity_and_hasher(capacity, S::default()); while let Some((key, value)) = map.next_entry()? { values.insert(key, value); @@ -82,8 +103,6 @@ where } } -use crate::IndexSet; - impl Serialize for IndexSet where T: Serialize, @@ -113,8 +132,8 @@ where where A: SeqAccess<'de>, { - let mut values = - IndexSet::with_capacity_and_hasher(seq.size_hint().unwrap_or(0), S::default()); + let capacity = cautious_capacity::(seq.size_hint()); + let mut values = IndexSet::with_capacity_and_hasher(capacity, S::default()); while let Some(value) = seq.next_element()? { values.insert(value); From 7e64de13957ba85df295b3167b4f9dd0ea73d264 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 28 Feb 2024 17:11:51 -0800 Subject: [PATCH 184/236] Simplify `cautious_capacity` since `Bucket` is never a ZST --- src/serde.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/serde.rs b/src/serde.rs index 44760145..25546d53 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -23,14 +23,10 @@ use crate::{Bucket, IndexMap, IndexSet}; pub(crate) fn cautious_capacity(hint: Option) -> usize { const MAX_PREALLOC_BYTES: usize = 1024 * 1024; - if mem::size_of::>() == 0 { - 0 - } else { - cmp::min( - hint.unwrap_or(0), - MAX_PREALLOC_BYTES / mem::size_of::>(), - ) - } + cmp::min( + hint.unwrap_or(0), + MAX_PREALLOC_BYTES / mem::size_of::>(), + ) } impl Serialize for IndexMap From 271b5ae7a4c99d6e5c99c2ce1788ca025da0e5c3 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 28 Feb 2024 17:20:35 -0800 Subject: [PATCH 185/236] Release 2.2.4 --- Cargo.toml | 2 +- RELEASES.md | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 60bda34b..95216563 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "indexmap" edition = "2021" -version = "2.2.3" +version = "2.2.4" 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 05d53af6..6d874237 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,11 @@ # Releases +## 2.2.4 + +- Added an `insert_sorted` method on `IndexMap`, `IndexSet`, and `VacantEntry`. +- Avoid hashing for lookups in single-entry maps. +- Limit preallocated memory in `serde` deserializers. + ## 2.2.3 - Added `move_index` and `swap_indices` methods to `IndexedEntry`, From ae38b913022251230c6b5fbe3df3706e300449ad Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Feb 2024 11:32:30 +0000 Subject: [PATCH 186/236] Add `borsh` dep to Cargo manifest --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 95216563..13a54dea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ equivalent = { version = "1.0", default-features = false } arbitrary = { version = "1.0", optional = true, default-features = false } quickcheck = { version = "1.0", optional = true, default-features = false } serde = { version = "1.0", optional = true, default-features = false } +borsh = { version = "1.2", optional = true, default-features = false } rayon = { version = "1.5.3", optional = true } # Internal feature, only used when building as part of rustc, @@ -54,7 +55,7 @@ no-dev-version = true tag-name = "{{version}}" [package.metadata.docs.rs] -features = ["arbitrary", "quickcheck", "serde", "rayon"] +features = ["arbitrary", "quickcheck", "serde", "borsh", "rayon"] rustdoc-args = ["--cfg", "docsrs"] [workspace] From 0804a16e24d2c8e0ed5e466df195fc358bb91b48 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Feb 2024 11:33:19 +0000 Subject: [PATCH 187/236] Implement `borsh` serialization routines --- src/borsh.rs | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 6 ++++ 2 files changed, 102 insertions(+) create mode 100644 src/borsh.rs diff --git a/src/borsh.rs b/src/borsh.rs new file mode 100644 index 00000000..9338f435 --- /dev/null +++ b/src/borsh.rs @@ -0,0 +1,96 @@ +#![cfg_attr(docsrs, doc(cfg(feature = "borsh")))] + +use alloc::vec::Vec; +use core::hash::BuildHasher; +use core::hash::Hash; +use core::iter::ExactSizeIterator; +use core::mem::size_of; + +use borsh::error::ERROR_ZST_FORBIDDEN; +use borsh::io::{Error, ErrorKind, Read, Result, Write}; +use borsh::{BorshDeserialize, BorshSerialize}; + +use crate::map::IndexMap; +use crate::set::IndexSet; + +impl BorshSerialize for IndexMap +where + K: BorshSerialize, + V: BorshSerialize, + H: BuildHasher, +{ + #[inline] + fn serialize(&self, writer: &mut W) -> Result<()> { + check_zst::()?; + + let iterator = self.iter(); + + u32::try_from(iterator.len()) + .map_err(|_| ErrorKind::InvalidData)? + .serialize(writer)?; + + for (key, value) in iterator { + key.serialize(writer)?; + value.serialize(writer)?; + } + + Ok(()) + } +} + +impl BorshDeserialize for IndexMap +where + K: BorshDeserialize + Eq + Hash, + V: BorshDeserialize, + H: BuildHasher + Default, +{ + #[inline] + fn deserialize_reader(reader: &mut R) -> Result { + check_zst::()?; + let vec = >::deserialize_reader(reader)?; + Ok(vec.into_iter().collect::>()) + } +} + +impl BorshSerialize for IndexSet +where + T: BorshSerialize, + H: BuildHasher, +{ + #[inline] + fn serialize(&self, writer: &mut W) -> Result<()> { + check_zst::()?; + + let iterator = self.iter(); + + u32::try_from(iterator.len()) + .map_err(|_| ErrorKind::InvalidData)? + .serialize(writer)?; + + for item in iterator { + item.serialize(writer)?; + } + + Ok(()) + } +} + +impl BorshDeserialize for IndexSet +where + T: BorshDeserialize + Eq + Hash, + H: BuildHasher + Default, +{ + #[inline] + fn deserialize_reader(reader: &mut R) -> Result { + check_zst::()?; + let vec = >::deserialize_reader(reader)?; + Ok(vec.into_iter().collect::>()) + } +} + +fn check_zst() -> Result<()> { + if size_of::() == 0 { + return Err(Error::new(ErrorKind::InvalidData, ERROR_ZST_FORBIDDEN)); + } + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index b88c1bce..d6d3ede9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,6 +35,8 @@ //! to [`IndexMap`] and [`IndexSet`]. Alternative implementations for //! (de)serializing [`IndexMap`] as an ordered sequence are available in the //! [`map::serde_seq`] module. +//! * `borsh`: Adds implementations for [`BorshSerialize`] and [`BorshDeserialize`] +//! to [`IndexMap`] and [`IndexSet`]. //! * `arbitrary`: Adds implementations for the [`arbitrary::Arbitrary`] trait //! to [`IndexMap`] and [`IndexSet`]. //! * `quickcheck`: Adds implementations for the [`quickcheck::Arbitrary`] trait @@ -46,6 +48,8 @@ //! [`no_std`]: #no-standard-library-targets //! [`Serialize`]: `::serde::Serialize` //! [`Deserialize`]: `::serde::Deserialize` +//! [`BorshSerialize`]: `::borsh::BorshSerialize` +//! [`BorshDeserialize`]: `::borsh::BorshDeserialize` //! [`arbitrary::Arbitrary`]: `::arbitrary::Arbitrary` //! [`quickcheck::Arbitrary`]: `::quickcheck::Arbitrary` //! @@ -110,6 +114,8 @@ use alloc::vec::{self, Vec}; mod arbitrary; #[macro_use] mod macros; +#[cfg(feature = "borsh")] +mod borsh; mod mutable_keys; #[cfg(feature = "serde")] mod serde; From c610e14ea059dc8e99359edf5fa4a1f53274a73b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Feb 2024 14:32:32 +0000 Subject: [PATCH 188/236] Add `borsh` serialization roundtrip tests --- src/borsh.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/borsh.rs b/src/borsh.rs index 9338f435..24899acb 100644 --- a/src/borsh.rs +++ b/src/borsh.rs @@ -94,3 +94,32 @@ fn check_zst() -> Result<()> { } Ok(()) } + +#[cfg(test)] +mod borsh_tests { + use super::*; + + #[test] + fn map_borsh_roundtrip() { + let original_map: IndexMap = { + let mut map = IndexMap::new(); + map.insert(1, 2); + map.insert(3, 4); + map.insert(5, 6); + map + }; + let serialized_map = borsh::to_vec(&original_map).unwrap(); + let deserialized_map: IndexMap = + BorshDeserialize::try_from_slice(&serialized_map).unwrap(); + assert_eq!(original_map, deserialized_map); + } + + #[test] + fn set_borsh_roundtrip() { + let original_map: IndexSet = [1, 2, 3, 4, 5, 6].into_iter().collect(); + let serialized_map = borsh::to_vec(&original_map).unwrap(); + let deserialized_map: IndexSet = + BorshDeserialize::try_from_slice(&serialized_map).unwrap(); + assert_eq!(original_map, deserialized_map); + } +} From 6ad3e42dc34a0f902090aa69e0aa0ce82508a4db Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 26 Feb 2024 10:14:09 +0000 Subject: [PATCH 189/236] Include `borsh` in CI workflow --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7631a57b..5b5448c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,8 @@ jobs: features: rustc-rayon - rust: stable features: serde + - rust: stable + features: borsh - rust: stable features: std - rust: beta From b8b1f52599d7fee3458bad36110d8a495ddee443 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 27 Feb 2024 16:03:14 -0800 Subject: [PATCH 190/236] ci: reduce features on MSRV --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b5448c9..312f4533 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,8 +121,10 @@ jobs: with: tool: cargo-hack - run: cargo +nightly hack generate-lockfile --remove-dev-deps -Z direct-minimal-versions - - name: Build - run: cargo build --verbose --all-features + - name: Build (nightly) + run: cargo +nightly build --verbose --all-features + - name: Build (MSRV) + run: cargo build --verbose --features arbitrary,quickcheck,serde,rayon # One job that "summarizes" the success state of this pipeline. This can then be added to branch # protection, rather than having to add each job separately. From 32793f1211fdf88b475e909259470011edfba152 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 27 Feb 2024 16:04:28 -0800 Subject: [PATCH 191/236] Don't require BuildHasher in BorshSerialize --- src/borsh.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/borsh.rs b/src/borsh.rs index 24899acb..2b3e06fe 100644 --- a/src/borsh.rs +++ b/src/borsh.rs @@ -17,7 +17,6 @@ impl BorshSerialize for IndexMap where K: BorshSerialize, V: BorshSerialize, - H: BuildHasher, { #[inline] fn serialize(&self, writer: &mut W) -> Result<()> { @@ -55,7 +54,6 @@ where impl BorshSerialize for IndexSet where T: BorshSerialize, - H: BuildHasher, { #[inline] fn serialize(&self, writer: &mut W) -> Result<()> { From b81a4d25e1703dfd574aee4551460ba571e1c870 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 27 Feb 2024 16:06:23 -0800 Subject: [PATCH 192/236] Use S for the BuildHasher parameter --- src/borsh.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/borsh.rs b/src/borsh.rs index 2b3e06fe..7b4afdc4 100644 --- a/src/borsh.rs +++ b/src/borsh.rs @@ -13,7 +13,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use crate::map::IndexMap; use crate::set::IndexSet; -impl BorshSerialize for IndexMap +impl BorshSerialize for IndexMap where K: BorshSerialize, V: BorshSerialize, @@ -37,21 +37,21 @@ where } } -impl BorshDeserialize for IndexMap +impl BorshDeserialize for IndexMap where K: BorshDeserialize + Eq + Hash, V: BorshDeserialize, - H: BuildHasher + Default, + S: BuildHasher + Default, { #[inline] fn deserialize_reader(reader: &mut R) -> Result { check_zst::()?; let vec = >::deserialize_reader(reader)?; - Ok(vec.into_iter().collect::>()) + Ok(vec.into_iter().collect::>()) } } -impl BorshSerialize for IndexSet +impl BorshSerialize for IndexSet where T: BorshSerialize, { @@ -73,16 +73,16 @@ where } } -impl BorshDeserialize for IndexSet +impl BorshDeserialize for IndexSet where T: BorshDeserialize + Eq + Hash, - H: BuildHasher + Default, + S: BuildHasher + Default, { #[inline] fn deserialize_reader(reader: &mut R) -> Result { check_zst::()?; let vec = >::deserialize_reader(reader)?; - Ok(vec.into_iter().collect::>()) + Ok(vec.into_iter().collect::>()) } } From 5d7bd5e6e3946678375ead7e72714162f3ad9a5e Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 29 Feb 2024 11:41:59 -0800 Subject: [PATCH 193/236] Release 2.2.5 --- Cargo.toml | 2 +- RELEASES.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 13a54dea..73ea6be5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "indexmap" edition = "2021" -version = "2.2.4" +version = "2.2.5" 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 6d874237..4e11c545 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,9 @@ # Releases +## 2.2.5 + +- Added optional `borsh` serialization support. + ## 2.2.4 - Added an `insert_sorted` method on `IndexMap`, `IndexSet`, and `VacantEntry`. From 210b027a67e42cd196f07cf8b2f0ee498a4b1a80 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 20 Mar 2024 16:00:06 -0700 Subject: [PATCH 194/236] Opt-in mutable access on IndexSet --- src/lib.rs | 3 +- src/map.rs | 10 +-- src/{mutable_keys.rs => map/mutable.rs} | 6 +- src/set.rs | 2 + src/set/mutable.rs | 86 +++++++++++++++++++++++++ 5 files changed, 94 insertions(+), 13 deletions(-) rename src/{mutable_keys.rs => map/mutable.rs} (94%) create mode 100644 src/set/mutable.rs diff --git a/src/lib.rs b/src/lib.rs index d6d3ede9..7d88ffef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,7 +19,7 @@ //! - The [`Equivalent`] trait, which offers more flexible equality definitions //! between borrowed and owned versions of keys. //! - The [`MutableKeys`][map::MutableKeys] trait, which gives opt-in mutable -//! access to hash map keys. +//! access to map keys, and [`MutableValues`][set::MutableValues] for sets. //! //! ### Feature Flags //! @@ -116,7 +116,6 @@ mod arbitrary; mod macros; #[cfg(feature = "borsh")] mod borsh; -mod mutable_keys; #[cfg(feature = "serde")] mod serde; mod util; diff --git a/src/map.rs b/src/map.rs index 46965a7c..f6d548bf 100644 --- a/src/map.rs +++ b/src/map.rs @@ -3,6 +3,7 @@ mod core; mod iter; +mod mutable; mod slice; #[cfg(feature = "serde")] @@ -17,8 +18,8 @@ pub use self::core::{Entry, IndexedEntry, OccupiedEntry, VacantEntry}; pub use self::iter::{ Drain, IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Splice, Values, ValuesMut, }; +pub use self::mutable::MutableKeys; pub use self::slice::Slice; -pub use crate::mutable_keys::MutableKeys; #[cfg(feature = "rayon")] pub use crate::rayon::map as rayon; @@ -808,13 +809,6 @@ impl IndexMap { self.core.retain_in_order(move |k, v| keep(k, v)); } - pub(crate) fn retain_mut(&mut self, keep: F) - where - F: FnMut(&mut K, &mut V) -> bool, - { - self.core.retain_in_order(keep); - } - /// Sort the map’s key-value pairs by the default ordering of the keys. /// /// This is a stable sort -- but equivalent keys should not normally coexist in diff --git a/src/mutable_keys.rs b/src/map/mutable.rs similarity index 94% rename from src/mutable_keys.rs rename to src/map/mutable.rs index 917af8c6..f45dd5e2 100644 --- a/src/mutable_keys.rs +++ b/src/map/mutable.rs @@ -6,7 +6,7 @@ use super::{Bucket, Entries, Equivalent, IndexMap}; /// /// These methods expose `&mut K`, mutable references to the key as it is stored /// in the map. -/// You are allowed to modify the keys in the hashmap **if the modification +/// You are allowed to modify the keys in the map **if the modification /// does not change the key’s hash and equality**. /// /// If keys are modified erroneously, you can no longer look them up. @@ -49,7 +49,7 @@ pub trait MutableKeys: private::Sealed { F: FnMut(&mut Self::Key, &mut Self::Value) -> bool; } -/// Opt-in mutable access to keys. +/// Opt-in mutable access to [`IndexMap`] keys. /// /// See [`MutableKeys`] for more information. impl MutableKeys for IndexMap @@ -79,7 +79,7 @@ where where F: FnMut(&mut K, &mut V) -> bool, { - self.retain_mut(keep) + self.core.retain_in_order(keep); } } diff --git a/src/set.rs b/src/set.rs index 5774349d..671b2fa9 100644 --- a/src/set.rs +++ b/src/set.rs @@ -1,6 +1,7 @@ //! A hash set implemented using [`IndexMap`] mod iter; +mod mutable; mod slice; #[cfg(test)] @@ -9,6 +10,7 @@ mod tests; pub use self::iter::{ Difference, Drain, Intersection, IntoIter, Iter, Splice, SymmetricDifference, Union, }; +pub use self::mutable::MutableValues; pub use self::slice::Slice; #[cfg(feature = "rayon")] diff --git a/src/set/mutable.rs b/src/set/mutable.rs new file mode 100644 index 00000000..3acecf3f --- /dev/null +++ b/src/set/mutable.rs @@ -0,0 +1,86 @@ +use core::hash::{BuildHasher, Hash}; + +use super::{Equivalent, IndexSet}; +use crate::map::MutableKeys; + +/// Opt-in mutable access to [`IndexSet`] values. +/// +/// These methods expose `&mut T`, mutable references to the value as it is stored +/// in the set. +/// You are allowed to modify the values in the set **if the modification +/// does not change the value’s hash and equality**. +/// +/// If values are modified erroneously, you can no longer look them up. +/// This is sound (memory safe) but a logical error hazard (just like +/// implementing `PartialEq`, `Eq`, or `Hash` incorrectly would be). +/// +/// `use` this trait to enable its methods for `IndexSet`. +/// +/// This trait is sealed and cannot be implemented for types outside this crate. +pub trait MutableValues: private::Sealed { + type Value; + + /// Return item index and mutable reference to the value + /// + /// Computes in **O(1)** time (average). + fn get_full_mut2(&mut self, value: &Q) -> Option<(usize, &mut Self::Value)> + where + Q: Hash + Equivalent; + + /// Return mutable reference to the value at an index. + /// + /// Valid indices are *0 <= index < self.len()* + /// + /// Computes in **O(1)** time. + fn get_index_mut2(&mut self, index: usize) -> Option<&mut Self::Value>; + + /// Scan through each value in the set and keep those where the + /// closure `keep` returns `true`. + /// + /// The values are visited in order, and remaining values keep their order. + /// + /// Computes in **O(n)** time (average). + fn retain2(&mut self, keep: F) + where + F: FnMut(&mut Self::Value) -> bool; +} + +/// Opt-in mutable access to [`IndexSet`] values. +/// +/// See [`MutableValues`] for more information. +impl MutableValues for IndexSet +where + S: BuildHasher, +{ + type Value = T; + + fn get_full_mut2(&mut self, value: &Q) -> Option<(usize, &mut T)> + where + Q: Hash + Equivalent, + { + match self.map.get_full_mut2(value) { + Some((index, value, ())) => Some((index, value)), + None => None, + } + } + + fn get_index_mut2(&mut self, index: usize) -> Option<&mut T> { + match self.map.get_index_mut2(index) { + Some((value, ())) => Some(value), + None => None, + } + } + + fn retain2(&mut self, mut keep: F) + where + F: FnMut(&mut T) -> bool, + { + self.map.retain2(move |value, ()| keep(value)); + } +} + +mod private { + pub trait Sealed {} + + impl Sealed for super::IndexSet {} +} From 0060546c3040e11b4f34e2e0214d640059731d21 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 22 Mar 2024 16:34:11 -0700 Subject: [PATCH 195/236] Release 2.2.6 --- Cargo.toml | 2 +- RELEASES.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 73ea6be5..f82ac3b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "indexmap" edition = "2021" -version = "2.2.5" +version = "2.2.6" 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 4e11c545..8c033db0 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,9 @@ # Releases +## 2.2.6 + +- Added trait `MutableValues` for opt-in mutable access to set values. + ## 2.2.5 - Added optional `borsh` serialization support. From b76ff73d54745fbf4126008b043ea22b1755736b Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 22 Mar 2024 16:51:08 -0700 Subject: [PATCH 196/236] Fix `clippy::multiple_bound_locations` --- src/map.rs | 60 ++++++++++++++++++------------------ src/map/core/raw_entry_v1.rs | 16 +++++----- src/map/mutable.rs | 11 +++---- src/set.rs | 48 ++++++++++++++--------------- src/set/mutable.rs | 8 ++--- 5 files changed, 70 insertions(+), 73 deletions(-) diff --git a/src/map.rs b/src/map.rs index f6d548bf..82824c90 100644 --- a/src/map.rs +++ b/src/map.rs @@ -534,9 +534,9 @@ where /// Return `true` if an equivalent to `key` exists in the map. /// /// Computes in **O(1)** time (average). - pub fn contains_key(&self, key: &Q) -> bool + pub fn contains_key(&self, key: &Q) -> bool where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { self.get_index_of(key).is_some() } @@ -545,9 +545,9 @@ where /// else `None`. /// /// Computes in **O(1)** time (average). - pub fn get(&self, key: &Q) -> Option<&V> + pub fn get(&self, key: &Q) -> Option<&V> where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { if let Some(i) = self.get_index_of(key) { let entry = &self.as_entries()[i]; @@ -561,9 +561,9 @@ where /// if it is present, else `None`. /// /// Computes in **O(1)** time (average). - pub fn get_key_value(&self, key: &Q) -> Option<(&K, &V)> + pub fn get_key_value(&self, key: &Q) -> Option<(&K, &V)> where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { if let Some(i) = self.get_index_of(key) { let entry = &self.as_entries()[i]; @@ -574,9 +574,9 @@ where } /// Return item index, key and value - pub fn get_full(&self, key: &Q) -> Option<(usize, &K, &V)> + pub fn get_full(&self, key: &Q) -> Option<(usize, &K, &V)> where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { if let Some(i) = self.get_index_of(key) { let entry = &self.as_entries()[i]; @@ -589,9 +589,9 @@ where /// Return item index, if it exists in the map /// /// Computes in **O(1)** time (average). - pub fn get_index_of(&self, key: &Q) -> Option + pub fn get_index_of(&self, key: &Q) -> Option where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { match self.as_entries() { [] => None, @@ -603,9 +603,9 @@ where } } - pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> + pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { if let Some(i) = self.get_index_of(key) { let entry = &mut self.as_entries_mut()[i]; @@ -615,9 +615,9 @@ where } } - pub fn get_full_mut(&mut self, key: &Q) -> Option<(usize, &K, &mut V)> + pub fn get_full_mut(&mut self, key: &Q) -> Option<(usize, &K, &mut V)> where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { if let Some(i) = self.get_index_of(key) { let entry = &mut self.as_entries_mut()[i]; @@ -636,9 +636,9 @@ where /// [`.shift_remove(key)`][Self::shift_remove] instead. #[deprecated(note = "`remove` disrupts the map order -- \ use `swap_remove` or `shift_remove` for explicit behavior.")] - pub fn remove(&mut self, key: &Q) -> Option + pub fn remove(&mut self, key: &Q) -> Option where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { self.swap_remove(key) } @@ -651,9 +651,9 @@ where /// use [`.shift_remove_entry(key)`][Self::shift_remove_entry] instead. #[deprecated(note = "`remove_entry` disrupts the map order -- \ use `swap_remove_entry` or `shift_remove_entry` for explicit behavior.")] - pub fn remove_entry(&mut self, key: &Q) -> Option<(K, V)> + pub fn remove_entry(&mut self, key: &Q) -> Option<(K, V)> where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { self.swap_remove_entry(key) } @@ -668,9 +668,9 @@ where /// Return `None` if `key` is not in map. /// /// Computes in **O(1)** time (average). - pub fn swap_remove(&mut self, key: &Q) -> Option + pub fn swap_remove(&mut self, key: &Q) -> Option where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { self.swap_remove_full(key).map(third) } @@ -684,9 +684,9 @@ where /// Return `None` if `key` is not in map. /// /// Computes in **O(1)** time (average). - pub fn swap_remove_entry(&mut self, key: &Q) -> Option<(K, V)> + pub fn swap_remove_entry(&mut self, key: &Q) -> Option<(K, V)> where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { match self.swap_remove_full(key) { Some((_, key, value)) => Some((key, value)), @@ -704,9 +704,9 @@ where /// Return `None` if `key` is not in map. /// /// Computes in **O(1)** time (average). - pub fn swap_remove_full(&mut self, key: &Q) -> Option<(usize, K, V)> + pub fn swap_remove_full(&mut self, key: &Q) -> Option<(usize, K, V)> where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { match self.as_entries() { [x] if key.equivalent(&x.key) => { @@ -731,9 +731,9 @@ where /// Return `None` if `key` is not in map. /// /// Computes in **O(n)** time (average). - pub fn shift_remove(&mut self, key: &Q) -> Option + pub fn shift_remove(&mut self, key: &Q) -> Option where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { self.shift_remove_full(key).map(third) } @@ -747,9 +747,9 @@ where /// Return `None` if `key` is not in map. /// /// Computes in **O(n)** time (average). - pub fn shift_remove_entry(&mut self, key: &Q) -> Option<(K, V)> + pub fn shift_remove_entry(&mut self, key: &Q) -> Option<(K, V)> where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { match self.shift_remove_full(key) { Some((_, key, value)) => Some((key, value)), @@ -767,9 +767,9 @@ where /// Return `None` if `key` is not in map. /// /// Computes in **O(n)** time (average). - pub fn shift_remove_full(&mut self, key: &Q) -> Option<(usize, K, V)> + pub fn shift_remove_full(&mut self, key: &Q) -> Option<(usize, K, V)> where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { match self.as_entries() { [x] if key.equivalent(&x.key) => { diff --git a/src/map/core/raw_entry_v1.rs b/src/map/core/raw_entry_v1.rs index a3848149..87e532d5 100644 --- a/src/map/core/raw_entry_v1.rs +++ b/src/map/core/raw_entry_v1.rs @@ -198,18 +198,18 @@ impl fmt::Debug for RawEntryBuilder<'_, K, V, S> { impl<'a, K, V, S> RawEntryBuilder<'a, K, V, S> { /// Access an entry by key. - pub fn from_key(self, key: &Q) -> Option<(&'a K, &'a V)> + pub fn from_key(self, key: &Q) -> Option<(&'a K, &'a V)> where S: BuildHasher, - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { self.map.get_key_value(key) } /// Access an entry by a key and its hash. - pub fn from_key_hashed_nocheck(self, hash: u64, key: &Q) -> Option<(&'a K, &'a V)> + pub fn from_key_hashed_nocheck(self, hash: u64, key: &Q) -> Option<(&'a K, &'a V)> where - Q: Equivalent, + Q: ?Sized + Equivalent, { let hash = HashValue(hash as usize); let i = self.map.core.get_index_of(hash, key)?; @@ -265,19 +265,19 @@ impl fmt::Debug for RawEntryBuilderMut<'_, K, V, S> { impl<'a, K, V, S> RawEntryBuilderMut<'a, K, V, S> { /// Access an entry by key. - pub fn from_key(self, key: &Q) -> RawEntryMut<'a, K, V, S> + pub fn from_key(self, key: &Q) -> RawEntryMut<'a, K, V, S> where S: BuildHasher, - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { let hash = self.map.hash(key); self.from_key_hashed_nocheck(hash.get(), key) } /// Access an entry by a key and its hash. - pub fn from_key_hashed_nocheck(self, hash: u64, key: &Q) -> RawEntryMut<'a, K, V, S> + pub fn from_key_hashed_nocheck(self, hash: u64, key: &Q) -> RawEntryMut<'a, K, V, S> where - Q: Equivalent, + Q: ?Sized + Equivalent, { self.from_hash(hash, |k| Q::equivalent(key, k)) } diff --git a/src/map/mutable.rs b/src/map/mutable.rs index f45dd5e2..7df32594 100644 --- a/src/map/mutable.rs +++ b/src/map/mutable.rs @@ -23,12 +23,9 @@ pub trait MutableKeys: private::Sealed { /// Return item index, mutable reference to key and value /// /// Computes in **O(1)** time (average). - fn get_full_mut2( - &mut self, - key: &Q, - ) -> Option<(usize, &mut Self::Key, &mut Self::Value)> + fn get_full_mut2(&mut self, key: &Q) -> Option<(usize, &mut Self::Key, &mut Self::Value)> where - Q: Hash + Equivalent; + Q: ?Sized + Hash + Equivalent; /// Return mutable reference to key and value at an index. /// @@ -59,9 +56,9 @@ where type Key = K; type Value = V; - fn get_full_mut2(&mut self, key: &Q) -> Option<(usize, &mut K, &mut V)> + fn get_full_mut2(&mut self, key: &Q) -> Option<(usize, &mut K, &mut V)> where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { if let Some(i) = self.get_index_of(key) { let entry = &mut self.as_entries_mut()[i]; diff --git a/src/set.rs b/src/set.rs index 671b2fa9..b5bd05f1 100644 --- a/src/set.rs +++ b/src/set.rs @@ -510,9 +510,9 @@ where /// Return `true` if an equivalent to `value` exists in the set. /// /// Computes in **O(1)** time (average). - pub fn contains(&self, value: &Q) -> bool + pub fn contains(&self, value: &Q) -> bool where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { self.map.contains_key(value) } @@ -521,17 +521,17 @@ where /// else `None`. /// /// Computes in **O(1)** time (average). - pub fn get(&self, value: &Q) -> Option<&T> + pub fn get(&self, value: &Q) -> Option<&T> where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { self.map.get_key_value(value).map(|(x, &())| x) } /// Return item index and value - pub fn get_full(&self, value: &Q) -> Option<(usize, &T)> + pub fn get_full(&self, value: &Q) -> Option<(usize, &T)> where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { self.map.get_full(value).map(|(i, x, &())| (i, x)) } @@ -539,9 +539,9 @@ where /// Return item index, if it exists in the set /// /// Computes in **O(1)** time (average). - pub fn get_index_of(&self, value: &Q) -> Option + pub fn get_index_of(&self, value: &Q) -> Option where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { self.map.get_index_of(value) } @@ -554,9 +554,9 @@ where /// [`.shift_remove(value)`][Self::shift_remove] instead. #[deprecated(note = "`remove` disrupts the set order -- \ use `swap_remove` or `shift_remove` for explicit behavior.")] - pub fn remove(&mut self, value: &Q) -> bool + pub fn remove(&mut self, value: &Q) -> bool where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { self.swap_remove(value) } @@ -570,9 +570,9 @@ where /// Return `false` if `value` was not in the set. /// /// Computes in **O(1)** time (average). - pub fn swap_remove(&mut self, value: &Q) -> bool + pub fn swap_remove(&mut self, value: &Q) -> bool where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { self.map.swap_remove(value).is_some() } @@ -586,9 +586,9 @@ where /// Return `false` if `value` was not in the set. /// /// Computes in **O(n)** time (average). - pub fn shift_remove(&mut self, value: &Q) -> bool + pub fn shift_remove(&mut self, value: &Q) -> bool where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { self.map.shift_remove(value).is_some() } @@ -602,9 +602,9 @@ where /// [`.shift_take(value)`][Self::shift_take] instead. #[deprecated(note = "`take` disrupts the set order -- \ use `swap_take` or `shift_take` for explicit behavior.")] - pub fn take(&mut self, value: &Q) -> Option + pub fn take(&mut self, value: &Q) -> Option where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { self.swap_take(value) } @@ -619,9 +619,9 @@ where /// Return `None` if `value` was not in the set. /// /// Computes in **O(1)** time (average). - pub fn swap_take(&mut self, value: &Q) -> Option + pub fn swap_take(&mut self, value: &Q) -> Option where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { self.map.swap_remove_entry(value).map(|(x, ())| x) } @@ -636,9 +636,9 @@ where /// Return `None` if `value` was not in the set. /// /// Computes in **O(n)** time (average). - pub fn shift_take(&mut self, value: &Q) -> Option + pub fn shift_take(&mut self, value: &Q) -> Option where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { self.map.shift_remove_entry(value).map(|(x, ())| x) } @@ -650,9 +650,9 @@ where /// the position of what used to be the last element!** /// /// Return `None` if `value` was not in the set. - pub fn swap_remove_full(&mut self, value: &Q) -> Option<(usize, T)> + pub fn swap_remove_full(&mut self, value: &Q) -> Option<(usize, T)> where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { self.map.swap_remove_full(value).map(|(i, x, ())| (i, x)) } @@ -664,9 +664,9 @@ where /// **This perturbs the index of all of those elements!** /// /// Return `None` if `value` was not in the set. - pub fn shift_remove_full(&mut self, value: &Q) -> Option<(usize, T)> + pub fn shift_remove_full(&mut self, value: &Q) -> Option<(usize, T)> where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { self.map.shift_remove_full(value).map(|(i, x, ())| (i, x)) } diff --git a/src/set/mutable.rs b/src/set/mutable.rs index 3acecf3f..20eaa112 100644 --- a/src/set/mutable.rs +++ b/src/set/mutable.rs @@ -23,9 +23,9 @@ pub trait MutableValues: private::Sealed { /// Return item index and mutable reference to the value /// /// Computes in **O(1)** time (average). - fn get_full_mut2(&mut self, value: &Q) -> Option<(usize, &mut Self::Value)> + fn get_full_mut2(&mut self, value: &Q) -> Option<(usize, &mut Self::Value)> where - Q: Hash + Equivalent; + Q: ?Sized + Hash + Equivalent; /// Return mutable reference to the value at an index. /// @@ -54,9 +54,9 @@ where { type Value = T; - fn get_full_mut2(&mut self, value: &Q) -> Option<(usize, &mut T)> + fn get_full_mut2(&mut self, value: &Q) -> Option<(usize, &mut T)> where - Q: Hash + Equivalent, + Q: ?Sized + Hash + Equivalent, { match self.map.get_full_mut2(value) { Some((index, value, ())) => Some((index, value)), From 33c1a7c771b255dc9376a36b7f2bfd39c5fefbcb Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 22 Mar 2024 16:53:49 -0700 Subject: [PATCH 197/236] Fix `unused_imports` --- src/borsh.rs | 1 - src/map/slice.rs | 1 - src/set/slice.rs | 1 - 3 files changed, 3 deletions(-) diff --git a/src/borsh.rs b/src/borsh.rs index 7b4afdc4..c485bd52 100644 --- a/src/borsh.rs +++ b/src/borsh.rs @@ -3,7 +3,6 @@ use alloc::vec::Vec; use core::hash::BuildHasher; use core::hash::Hash; -use core::iter::ExactSizeIterator; use core::mem::size_of; use borsh::error::ERROR_ZST_FORBIDDEN; diff --git a/src/map/slice.rs b/src/map/slice.rs index ce001ee8..b2f00f48 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -426,7 +426,6 @@ impl_index!( #[cfg(test)] mod tests { use super::*; - use alloc::vec::Vec; #[test] fn slice_index() { diff --git a/src/set/slice.rs b/src/set/slice.rs index be006c97..9fc208c7 100644 --- a/src/set/slice.rs +++ b/src/set/slice.rs @@ -293,7 +293,6 @@ impl_index!( #[cfg(test)] mod tests { use super::*; - use alloc::vec::Vec; #[test] fn slice_index() { From ac2a8a5a403b8e0956ff765bac1fb0b2a1a70f2b Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Tue, 21 May 2024 18:29:17 +0700 Subject: [PATCH 198/236] deps(dev): Update `itertools` --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f82ac3b0..58b39865 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ default-features = false features = ["raw"] [dev-dependencies] -itertools = "0.12" +itertools = "0.13" rand = {version = "0.8", features = ["small_rng"] } quickcheck = { version = "1.0", default-features = false } fnv = "1.0" From 8222a59a857ee57d5f3cc616d9ba287d766d5661 Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Fri, 26 Jul 2024 10:23:14 +0700 Subject: [PATCH 199/236] Fix missing indentation in doc comment. --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 7d88ffef..9ffcd420 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,7 +94,7 @@ //! `default-features = false` to your dependency specification. //! //! - Creating maps and sets using [`new`][IndexMap::new] and -//! [`with_capacity`][IndexMap::with_capacity] is unavailable without `std`. +//! [`with_capacity`][IndexMap::with_capacity] is unavailable without `std`. //! Use methods [`IndexMap::default`], [`with_hasher`][IndexMap::with_hasher], //! [`with_capacity_and_hasher`][IndexMap::with_capacity_and_hasher] instead. //! A no-std compatible hasher will be needed as well, for example From 65c3c46e37f0f82893285d94b52347c7c64ca4ef Mon Sep 17 00:00:00 2001 From: Ed Page Date: Sat, 27 Jul 2024 21:05:04 -0500 Subject: [PATCH 200/236] feat(map): Add MutableEntryKey I added `pub(crate) fn key_mut` to the different `Entry` types to limit how much of their internals get exposed (as otherwise I'd have had to make their fields `pub(crate)`. --- src/map.rs | 1 + src/map/core/entry.rs | 12 +++++++ src/map/mutable.rs | 73 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 85 insertions(+), 1 deletion(-) diff --git a/src/map.rs b/src/map.rs index 82824c90..ab931a9a 100644 --- a/src/map.rs +++ b/src/map.rs @@ -18,6 +18,7 @@ pub use self::core::{Entry, IndexedEntry, OccupiedEntry, VacantEntry}; pub use self::iter::{ Drain, IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Splice, Values, ValuesMut, }; +pub use self::mutable::MutableEntryKey; pub use self::mutable::MutableKeys; pub use self::slice::Slice; diff --git a/src/map/core/entry.rs b/src/map/core/entry.rs index 6c310707..ab45ecc1 100644 --- a/src/map/core/entry.rs +++ b/src/map/core/entry.rs @@ -144,6 +144,10 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { &self.raw.bucket().key } + pub(crate) fn key_mut(&mut self) -> &mut K { + &mut self.raw.bucket_mut().key + } + /// Gets a reference to the entry's value in the map. pub fn get(&self) -> &V { &self.raw.bucket().value @@ -297,6 +301,10 @@ impl<'a, K, V> VacantEntry<'a, K, V> { &self.key } + pub(crate) fn key_mut(&mut self) -> &mut K { + &mut self.key + } + /// Takes ownership of the key, leaving the entry vacant. pub fn into_key(self) -> K { self.key @@ -373,6 +381,10 @@ impl<'a, K, V> IndexedEntry<'a, K, V> { &self.map.entries[self.index].key } + pub(crate) fn key_mut(&mut self) -> &mut K { + &mut self.map.entries[self.index].key + } + /// Gets a reference to the entry's value in the map. pub fn get(&self) -> &V { &self.map.entries[self.index].value diff --git a/src/map/mutable.rs b/src/map/mutable.rs index 7df32594..4aa22943 100644 --- a/src/map/mutable.rs +++ b/src/map/mutable.rs @@ -1,6 +1,8 @@ use core::hash::{BuildHasher, Hash}; -use super::{Bucket, Entries, Equivalent, IndexMap}; +use super::{ + Bucket, Entries, Entry, Equivalent, IndexMap, IndexedEntry, OccupiedEntry, VacantEntry, +}; /// Opt-in mutable access to [`IndexMap`] keys. /// @@ -80,8 +82,77 @@ where } } +/// Opt-in mutable access to [`Entry`] keys. +/// +/// These methods expose `&mut K`, mutable references to the key as it is stored +/// in the map. +/// You are allowed to modify the keys in the map **if the modification +/// does not change the key’s hash and equality**. +/// +/// If keys are modified erroneously, you can no longer look them up. +/// This is sound (memory safe) but a logical error hazard (just like +/// implementing `PartialEq`, `Eq`, or `Hash` incorrectly would be). +/// +/// `use` this trait to enable its methods for `Entry`. +/// +/// This trait is sealed and cannot be implemented for types outside this crate. +pub trait MutableEntryKey: private::Sealed { + type Key; + fn key_mut(&mut self) -> &mut Self::Key; +} + +/// Opt-in mutable access to [`Entry`] keys. +/// +/// 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(), + Entry::Vacant(e) => e.key_mut(), + } + } +} + +/// Opt-in mutable access to [`OccupiedEntry`] keys. +/// +/// See [`MutableEntryKey`] for more information. +impl MutableEntryKey for OccupiedEntry<'_, K, V> { + type Key = K; + fn key_mut(&mut self) -> &mut Self::Key { + self.key_mut() + } +} + +/// Opt-in mutable access to [`VacantEntry`] keys. +/// +/// See [`MutableEntryKey`] for more information. +impl MutableEntryKey for VacantEntry<'_, K, V> { + type Key = K; + fn key_mut(&mut self) -> &mut Self::Key { + self.key_mut() + } +} + +/// Opt-in mutable access to [`IndexedEntry`] keys. +/// +/// See [`MutableEntryKey`] for more information. +impl MutableEntryKey for IndexedEntry<'_, K, V> { + type Key = K; + fn key_mut(&mut self) -> &mut Self::Key { + self.key_mut() + } +} + mod private { pub trait Sealed {} impl Sealed for super::IndexMap {} + impl Sealed for super::Entry<'_, K, V> {} + impl Sealed for super::OccupiedEntry<'_, K, V> {} + impl Sealed for super::VacantEntry<'_, K, V> {} + impl Sealed for super::IndexedEntry<'_, K, V> {} } From 6049d518a04f92fb3e29392f929600de7b41f47f Mon Sep 17 00:00:00 2001 From: Ed Page Date: Sat, 27 Jul 2024 21:20:16 -0500 Subject: [PATCH 201/236] feat(map): Add MutableKeys::iter_mut2 --- src/map.rs | 2 +- src/map/iter.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++++ src/map/mutable.rs | 10 +++++++- src/map/tests.rs | 1 + 4 files changed, 72 insertions(+), 2 deletions(-) diff --git a/src/map.rs b/src/map.rs index ab931a9a..ea7e1b48 100644 --- a/src/map.rs +++ b/src/map.rs @@ -16,7 +16,7 @@ mod tests; pub use self::core::raw_entry_v1::{self, RawEntryApiV1}; pub use self::core::{Entry, IndexedEntry, OccupiedEntry, VacantEntry}; pub use self::iter::{ - Drain, IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Splice, Values, ValuesMut, + Drain, IntoIter, IntoKeys, IntoValues, Iter, IterMut, IterMut2, Keys, Splice, Values, ValuesMut, }; pub use self::mutable::MutableEntryKey; pub use self::mutable::MutableKeys; diff --git a/src/map/iter.rs b/src/map/iter.rs index 1ec3703c..9bbf9d9f 100644 --- a/src/map/iter.rs +++ b/src/map/iter.rs @@ -156,6 +156,67 @@ impl Default for IterMut<'_, K, V> { } } +/// A mutable iterator over the entries of an [`IndexMap`]. +/// +/// This `struct` is created by the [`MutableKeys::iter_mut2`][super::MutableKeys::iter_mut2] method. +/// See its documentation for more. +pub struct IterMut2<'a, K, V> { + iter: slice::IterMut<'a, Bucket>, +} + +impl<'a, K, V> IterMut2<'a, K, V> { + pub(super) fn new(entries: &'a mut [Bucket]) -> Self { + Self { + iter: entries.iter_mut(), + } + } + + /// Returns a slice of the remaining entries in the iterator. + pub fn as_slice(&self) -> &Slice { + Slice::from_slice(self.iter.as_slice()) + } + + /// Returns a mutable slice of the remaining entries in the iterator. + /// + /// To avoid creating `&mut` references that alias, this is forced to consume the iterator. + pub fn into_slice(self) -> &'a mut Slice { + Slice::from_mut_slice(self.iter.into_slice()) + } +} + +impl<'a, K, V> Iterator for IterMut2<'a, K, V> { + type Item = (&'a mut K, &'a mut V); + + iterator_methods!(Bucket::muts); +} + +impl DoubleEndedIterator for IterMut2<'_, K, V> { + double_ended_iterator_methods!(Bucket::muts); +} + +impl ExactSizeIterator for IterMut2<'_, K, V> { + fn len(&self) -> usize { + self.iter.len() + } +} + +impl FusedIterator for IterMut2<'_, K, V> {} + +impl fmt::Debug for IterMut2<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let iter = self.iter.as_slice().iter().map(Bucket::refs); + f.debug_list().entries(iter).finish() + } +} + +impl Default for IterMut2<'_, K, V> { + fn default() -> Self { + Self { + iter: [].iter_mut(), + } + } +} + /// An owning iterator over the entries of an [`IndexMap`]. /// /// This `struct` is created by the [`IndexMap::into_iter`] method diff --git a/src/map/mutable.rs b/src/map/mutable.rs index 4aa22943..c778971b 100644 --- a/src/map/mutable.rs +++ b/src/map/mutable.rs @@ -1,7 +1,8 @@ use core::hash::{BuildHasher, Hash}; use super::{ - Bucket, Entries, Entry, Equivalent, IndexMap, IndexedEntry, OccupiedEntry, VacantEntry, + Bucket, Entries, Entry, Equivalent, IndexMap, IndexedEntry, IterMut2, OccupiedEntry, + VacantEntry, }; /// Opt-in mutable access to [`IndexMap`] keys. @@ -36,6 +37,9 @@ pub trait MutableKeys: private::Sealed { /// Computes in **O(1)** time. fn get_index_mut2(&mut self, index: usize) -> Option<(&mut Self::Key, &mut Self::Value)>; + /// Return an iterator over the key-value pairs of the map, in their order + fn iter_mut2(&mut self) -> IterMut2<'_, Self::Key, Self::Value>; + /// Scan through each key-value pair in the map and keep those where the /// closure `keep` returns `true`. /// @@ -74,6 +78,10 @@ where self.as_entries_mut().get_mut(index).map(Bucket::muts) } + fn iter_mut2(&mut self) -> IterMut2<'_, Self::Key, Self::Value> { + IterMut2::new(self.as_entries_mut()) + } + fn retain2(&mut self, keep: F) where F: FnMut(&mut K, &mut V) -> bool, diff --git a/src/map/tests.rs b/src/map/tests.rs index bba78ff5..49541181 100644 --- a/src/map/tests.rs +++ b/src/map/tests.rs @@ -497,6 +497,7 @@ fn iter_default() { } assert_default::>(); assert_default::>(); + assert_default::>(); assert_default::>(); assert_default::>(); assert_default::>(); From 39f7cc097ac49ef4b47e99440644f11bc3d2b24c Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 31 Jul 2024 13:37:10 -0700 Subject: [PATCH 202/236] Release 2.3.0 --- Cargo.toml | 2 +- RELEASES.md | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 58b39865..bdd94be5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "indexmap" edition = "2021" -version = "2.2.6" +version = "2.3.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 8c033db0..3b30af76 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,11 @@ # Releases +## 2.3.0 + +- Added trait `MutableEntryKey` for opt-in mutable access to map entry keys. +- Added method `MutableKeys::iter_mut2` for opt-in mutable iteration of map + keys and values. + ## 2.2.6 - Added trait `MutableValues` for opt-in mutable access to set values. From 8c0a1cd4be7de02bee713433c449bcb251dcd994 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 13 Aug 2024 11:52:04 -0700 Subject: [PATCH 203/236] Add `append` methods --- src/map.rs | 29 +++++++++++++++++++++++++++++ src/set.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/src/map.rs b/src/map.rs index ea7e1b48..85310934 100644 --- a/src/map.rs +++ b/src/map.rs @@ -520,6 +520,35 @@ where { Splice::new(self, range, replace_with.into_iter()) } + + /// Moves all key-value pairs from `other` into `self`, leaving `other` empty. + /// + /// This is equivalent to calling [`insert`][Self::insert] for each + /// key-value pair from `other` in order, which means that for keys that + /// already exist in `self`, their value is updated in the current position. + /// + /// # Examples + /// + /// ``` + /// use indexmap::IndexMap; + /// + /// // Note: Key (3) is present in both maps. + /// let mut a = IndexMap::from([(3, "c"), (2, "b"), (1, "a")]); + /// let mut b = IndexMap::from([(3, "d"), (4, "e"), (5, "f")]); + /// let old_capacity = b.capacity(); + /// + /// a.append(&mut b); + /// + /// assert_eq!(a.len(), 5); + /// assert_eq!(b.len(), 0); + /// assert_eq!(b.capacity(), old_capacity); + /// + /// assert!(a.keys().eq(&[3, 2, 1, 4, 5])); + /// assert_eq!(a[&3], "d"); // "c" was overwritten. + /// ``` + pub fn append(&mut self, other: &mut IndexMap) { + self.extend(other.drain(..)); + } } impl IndexMap diff --git a/src/set.rs b/src/set.rs index b5bd05f1..835ccf02 100644 --- a/src/set.rs +++ b/src/set.rs @@ -501,6 +501,36 @@ where { Splice::new(self, range, replace_with.into_iter()) } + + /// Moves all values from `other` into `self`, leaving `other` empty. + /// + /// This is equivalent to calling [`insert`][Self::insert] for each value + /// from `other` in order, which means that values that already exist + /// in `self` are unchanged in their current position. + /// + /// See also [`union`][Self::union] to iterate the combined values by + /// reference, without modifying `self` or `other`. + /// + /// # Examples + /// + /// ``` + /// use indexmap::IndexSet; + /// + /// let mut a = IndexSet::from([3, 2, 1]); + /// let mut b = IndexSet::from([3, 4, 5]); + /// let old_capacity = b.capacity(); + /// + /// a.append(&mut b); + /// + /// assert_eq!(a.len(), 5); + /// assert_eq!(b.len(), 0); + /// assert_eq!(b.capacity(), old_capacity); + /// + /// assert!(a.iter().eq(&[3, 2, 1, 4, 5])); + /// ``` + pub fn append(&mut self, other: &mut IndexSet) { + self.map.append(&mut other.map); + } } impl IndexSet From 0b2b4b9a7829905292378cb99c392c44a0931850 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 13 Aug 2024 12:40:28 -0700 Subject: [PATCH 204/236] Release 2.4.0 --- Cargo.toml | 2 +- RELEASES.md | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bdd94be5..cdcc77b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "indexmap" edition = "2021" -version = "2.3.0" +version = "2.4.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 3b30af76..daf4493e 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,10 @@ # Releases +## 2.4.0 + +- Added methods `IndexMap::append` and `IndexSet::append`, moving all items from + one map or set into another, and leaving the original capacity for reuse. + ## 2.3.0 - Added trait `MutableEntryKey` for opt-in mutable access to map entry keys. From a288bf3b3a14ecc5f05006dd15f43759548b1469 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 13 Aug 2024 13:20:56 -0700 Subject: [PATCH 205/236] Add a README note for `ordermap` --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9112d528..e504109d 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ allows lookup of entries by either hash table key or numerical index. Note: this crate was originally released under the name `ordermap`, but it was renamed to `indexmap` to better reflect its features. +The [`ordermap`](https://crates.io/crates/ordermap) crate now exists +as a wrapper over `indexmap` with stronger ordering properties. # Background From b44138b20463ea9ec804f7213ccf68823126b682 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 21 Aug 2024 12:15:10 -0700 Subject: [PATCH 206/236] 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 207/236] 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 208/236] 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 209/236] 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 210/236] 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 211/236] 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 212/236] 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 213/236] 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 214/236] 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 215/236] 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 216/236] 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 From efa81da13beb802a4a1ee7d5ab733f73a75b01f8 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 12 Sep 2024 12:24:09 -0700 Subject: [PATCH 217/236] Test the various heuristics of `erase_indices` This is a targeted test to make sure we cover all the heuristic edge cases in `erase_indices`, used by `drain` and other methods. I found a failure from `cargo mutants` where we still passed tests after `erase_indices_sweep` was replaced with an empty body. I was concerned because that function contains `unsafe` code, so we *really* need it tested. It turns out that we do *sometimes* hit that in `quickcheck` tests, but might miss that if we're randomly unlucky, so this PR adds a new test that will hit all the edge cases every time. --- src/map/tests.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/map/tests.rs b/src/map/tests.rs index cbd4b4f2..d2707a44 100644 --- a/src/map/tests.rs +++ b/src/map/tests.rs @@ -541,6 +541,26 @@ fn into_values() { assert!(values.contains(&'c')); } +#[test] +fn drain_range() { + // Test the various heuristics of `erase_indices` + for range in [ + 0..0, // nothing erased + 10..90, // reinsert the few kept (..10 and 90..) + 80..90, // update the few to adjust (80..) + 20..30, // sweep everything + ] { + let mut vec = Vec::from_iter(0..100); + let mut map: IndexMap = (0..100).map(|i| (i, ())).collect(); + drop(vec.drain(range.clone())); + drop(map.drain(range)); + assert!(vec.iter().eq(map.keys())); + for (i, x) in vec.iter().enumerate() { + assert_eq!(map.get_index_of(x), Some(i)); + } + } +} + #[test] #[cfg(feature = "std")] fn from_array() { From 5d9cb11a68e496bfdb3191923d083f0d6e99cf84 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Mon, 16 Sep 2024 16:37:02 -0700 Subject: [PATCH 218/236] Derive `Clone` for `{map,set}::IntoIter` --- src/map/iter.rs | 1 + src/set/iter.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/map/iter.rs b/src/map/iter.rs index 9bbf9d9f..2943f18a 100644 --- a/src/map/iter.rs +++ b/src/map/iter.rs @@ -221,6 +221,7 @@ impl Default for IterMut2<'_, K, V> { /// /// This `struct` is created by the [`IndexMap::into_iter`] method /// (provided by the [`IntoIterator`] trait). See its documentation for more. +#[derive(Clone)] pub struct IntoIter { iter: vec::IntoIter>, } diff --git a/src/set/iter.rs b/src/set/iter.rs index 3f8033c2..31982760 100644 --- a/src/set/iter.rs +++ b/src/set/iter.rs @@ -88,6 +88,7 @@ impl Default for Iter<'_, T> { /// /// This `struct` is created by the [`IndexSet::into_iter`] method /// (provided by the [`IntoIterator`] trait). See its documentation for more. +#[derive(Clone)] pub struct IntoIter { iter: vec::IntoIter>, } From 5b0ed20b872618b0e57fa91f85228a174164fce8 Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Tue, 17 Sep 2024 23:38:39 +0700 Subject: [PATCH 219/236] docs: Improve doc formatting with backticks --- src/map.rs | 14 +++++++------- src/map/mutable.rs | 2 +- src/map/slice.rs | 8 ++++---- src/set.rs | 8 ++++---- src/set/mutable.rs | 2 +- src/set/slice.rs | 4 ++-- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/map.rs b/src/map.rs index 019c7f6d..946cb6fc 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1149,7 +1149,7 @@ impl IndexMap { /// Get a key-value pair by index /// - /// Valid indices are *0 <= index < self.len()* + /// Valid indices are `0 <= index < self.len()`. /// /// Computes in **O(1)** time. pub fn get_index(&self, index: usize) -> Option<(&K, &V)> { @@ -1158,7 +1158,7 @@ impl IndexMap { /// Get a key-value pair by index /// - /// Valid indices are *0 <= index < self.len()* + /// Valid indices are `0 <= index < self.len()`. /// /// Computes in **O(1)** time. pub fn get_index_mut(&mut self, index: usize) -> Option<(&K, &mut V)> { @@ -1167,7 +1167,7 @@ impl IndexMap { /// Get an entry in the map by index for in-place manipulation. /// - /// Valid indices are *0 <= index < self.len()* + /// Valid indices are `0 <= index < self.len()`. /// /// Computes in **O(1)** time. pub fn get_index_entry(&mut self, index: usize) -> Option> { @@ -1179,7 +1179,7 @@ impl IndexMap { /// Returns a slice of key-value pairs in the given range of indices. /// - /// Valid indices are *0 <= index < self.len()* + /// Valid indices are `0 <= index < self.len()`. /// /// Computes in **O(1)** time. pub fn get_range>(&self, range: R) -> Option<&Slice> { @@ -1190,7 +1190,7 @@ impl IndexMap { /// Returns a mutable slice of key-value pairs in the given range of indices. /// - /// Valid indices are *0 <= index < self.len()* + /// Valid indices are `0 <= index < self.len()`. /// /// Computes in **O(1)** time. pub fn get_range_mut>(&mut self, range: R) -> Option<&mut Slice> { @@ -1245,7 +1245,7 @@ impl IndexMap { /// Remove the key-value pair by index /// - /// Valid indices are *0 <= index < self.len()* + /// Valid indices are `0 <= index < self.len()`. /// /// Like [`Vec::swap_remove`], the pair is removed by swapping it with the /// last element of the map and popping it off. **This perturbs @@ -1258,7 +1258,7 @@ impl IndexMap { /// Remove the key-value pair by index /// - /// Valid indices are *0 <= index < self.len()* + /// Valid indices are `0 <= index < self.len()`. /// /// Like [`Vec::remove`], the pair is removed by shifting all of the /// elements that follow it, preserving their relative order. diff --git a/src/map/mutable.rs b/src/map/mutable.rs index 355eeb21..e429c8be 100644 --- a/src/map/mutable.rs +++ b/src/map/mutable.rs @@ -32,7 +32,7 @@ pub trait MutableKeys: private::Sealed { /// Return mutable reference to key and value at an index. /// - /// Valid indices are *0 <= index < self.len()* + /// Valid indices are `0 <= index < self.len()`. /// /// Computes in **O(1)** time. fn get_index_mut2(&mut self, index: usize) -> Option<(&mut Self::Key, &mut Self::Value)>; diff --git a/src/map/slice.rs b/src/map/slice.rs index b2f00f48..94795b70 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -73,21 +73,21 @@ impl Slice { /// Get a key-value pair by index. /// - /// Valid indices are *0 <= index < self.len()* + /// Valid indices are `0 <= index < self.len()`. pub fn get_index(&self, index: usize) -> Option<(&K, &V)> { self.entries.get(index).map(Bucket::refs) } /// Get a key-value pair by index, with mutable access to the value. /// - /// Valid indices are *0 <= index < self.len()* + /// Valid indices are `0 <= index < self.len()`. pub fn get_index_mut(&mut self, index: usize) -> Option<(&K, &mut V)> { self.entries.get_mut(index).map(Bucket::ref_mut) } /// Returns a slice of key-value pairs in the given range of indices. /// - /// Valid indices are *0 <= index < self.len()* + /// Valid indices are `0 <= index < self.len()`. pub fn get_range>(&self, range: R) -> Option<&Self> { let range = try_simplify_range(range, self.entries.len())?; self.entries.get(range).map(Slice::from_slice) @@ -95,7 +95,7 @@ impl Slice { /// Returns a mutable slice of key-value pairs in the given range of indices. /// - /// Valid indices are *0 <= index < self.len()* + /// Valid indices are `0 <= index < self.len()`. pub fn get_range_mut>(&mut self, range: R) -> Option<&mut Self> { let range = try_simplify_range(range, self.entries.len())?; self.entries.get_mut(range).map(Slice::from_mut_slice) diff --git a/src/set.rs b/src/set.rs index 250e31b3..5a91db95 100644 --- a/src/set.rs +++ b/src/set.rs @@ -983,7 +983,7 @@ impl IndexSet { /// Get a value by index /// - /// Valid indices are *0 <= index < self.len()* + /// Valid indices are `0 <= index < self.len()`. /// /// Computes in **O(1)** time. pub fn get_index(&self, index: usize) -> Option<&T> { @@ -992,7 +992,7 @@ impl IndexSet { /// Returns a slice of values in the given range of indices. /// - /// Valid indices are *0 <= index < self.len()* + /// Valid indices are `0 <= index < self.len()`. /// /// Computes in **O(1)** time. pub fn get_range>(&self, range: R) -> Option<&Slice> { @@ -1017,7 +1017,7 @@ impl IndexSet { /// Remove the value by index /// - /// Valid indices are *0 <= index < self.len()* + /// Valid indices are `0 <= index < self.len()`. /// /// Like [`Vec::swap_remove`], the value is removed by swapping it with the /// last element of the set and popping it off. **This perturbs @@ -1030,7 +1030,7 @@ impl IndexSet { /// Remove the value by index /// - /// Valid indices are *0 <= index < self.len()* + /// Valid indices are `0 <= index < self.len()`. /// /// Like [`Vec::remove`], the value is removed by shifting all of the /// elements that follow it, preserving their relative order. diff --git a/src/set/mutable.rs b/src/set/mutable.rs index 20eaa112..21615f34 100644 --- a/src/set/mutable.rs +++ b/src/set/mutable.rs @@ -29,7 +29,7 @@ pub trait MutableValues: private::Sealed { /// Return mutable reference to the value at an index. /// - /// Valid indices are *0 <= index < self.len()* + /// Valid indices are `0 <= index < self.len()`. /// /// Computes in **O(1)** time. fn get_index_mut2(&mut self, index: usize) -> Option<&mut Self::Value>; diff --git a/src/set/slice.rs b/src/set/slice.rs index 9fc208c7..f980e974 100644 --- a/src/set/slice.rs +++ b/src/set/slice.rs @@ -59,14 +59,14 @@ impl Slice { /// Get a value by index. /// - /// Valid indices are *0 <= index < self.len()* + /// Valid indices are `0 <= index < self.len()`. pub fn get_index(&self, index: usize) -> Option<&T> { self.entries.get(index).map(Bucket::key_ref) } /// Returns a slice of values in the given range of indices. /// - /// Valid indices are *0 <= index < self.len()* + /// Valid indices are `0 <= index < self.len()`. pub fn get_range>(&self, range: R) -> Option<&Self> { let range = try_simplify_range(range, self.entries.len())?; self.entries.get(range).map(Self::from_slice) From 267b83d701b4d010e01089aa528e26f31c244293 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 27 Sep 2024 13:49:05 -0700 Subject: [PATCH 220/236] Add an explicit bounds check in `move_index` It did already panic as expected, but with a confusing message if the `to` index was out of bounds. Now we have a direct bounds check for that at the start, just as there already was for the `from` index. --- src/map/core.rs | 1 + src/map/tests.rs | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/map/core.rs b/src/map/core.rs index 16e87c7c..ef6d5727 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -480,6 +480,7 @@ impl IndexMapCore { pub(super) fn move_index(&mut self, from: usize, to: usize) { let from_hash = self.entries[from].hash; + let _ = self.entries[to]; // explicit bounds check if from != to { // Use a sentinel index so other indices don't collide. update_index(&mut self.indices, from_hash, from, usize::MAX); diff --git a/src/map/tests.rs b/src/map/tests.rs index d2707a44..ca7e9d6c 100644 --- a/src/map/tests.rs +++ b/src/map/tests.rs @@ -813,3 +813,18 @@ fn test_partition_point() { assert_eq!(b.partition_point(|_, &x| x < 7), 4); assert_eq!(b.partition_point(|_, &x| x < 8), 5); } + +macro_rules! move_index_oob { + ($test:ident, $from:expr, $to:expr) => { + #[test] + #[should_panic(expected = "index out of bounds")] + fn $test() { + let mut map: IndexMap = (0..10).map(|k| (k, ())).collect(); + map.move_index($from, $to); + } + } +} +move_index_oob!(test_move_index_out_of_bounds_0_10, 0, 10); +move_index_oob!(test_move_index_out_of_bounds_0_max, 0, usize::MAX); +move_index_oob!(test_move_index_out_of_bounds_10_0, 10, 0); +move_index_oob!(test_move_index_out_of_bounds_max_0, usize::MAX, 0); From e577bf2556a40cb85d3befceeead50ee77ae508d Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 1 Oct 2024 08:11:01 -0700 Subject: [PATCH 221/236] Use `hashbrown::HashTable` instead of `RawTable` --- Cargo.toml | 3 +- src/lib.rs | 2 +- src/map/core.rs | 541 ++++++++++++++++++++--------------- src/map/core/entry.rs | 112 +++++--- src/map/core/raw.rs | 164 ----------- src/map/core/raw_entry_v1.rs | 78 ++--- src/map/tests.rs | 2 +- 7 files changed, 425 insertions(+), 477 deletions(-) delete mode 100644 src/map/core/raw.rs diff --git a/Cargo.toml b/Cargo.toml index 4590d455..52257db9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,9 +27,8 @@ rayon = { version = "1.5.3", optional = true } rustc-rayon = { package = "rustc-rayon", version = "0.5", optional = true } [dependencies.hashbrown] -version = "0.14.1" +version = "0.15.0" default-features = false -features = ["raw"] [dev-dependencies] itertools = "0.13" diff --git a/src/lib.rs b/src/lib.rs index 9ffcd420..3e16bc6e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -// We *mostly* avoid unsafe code, but `map::core::raw` allows it to use `RawTable` buckets. +// We *mostly* avoid unsafe code, but `Slice` allows it for DST casting. #![deny(unsafe_code)] #![warn(rust_2018_idioms)] #![no_std] diff --git a/src/map/core.rs b/src/map/core.rs index ef6d5727..f42cccbf 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -8,11 +8,10 @@ //! However, we should probably not let this show in the public API or docs. mod entry; -mod raw; pub mod raw_entry_v1; -use hashbrown::raw::RawTable; +use hashbrown::hash_table; use crate::vec::{self, Vec}; use crate::TryReserveError; @@ -20,16 +19,31 @@ use core::mem; use core::ops::RangeBounds; use crate::util::simplify_range; -use crate::{Bucket, Entries, Equivalent, HashValue}; +use crate::{Bucket, Equivalent, HashValue}; + +type Indices = hash_table::HashTable; +type Entries = Vec>; pub use entry::{Entry, IndexedEntry, OccupiedEntry, VacantEntry}; /// Core of the map that does not depend on S +#[derive(Debug)] pub(crate) struct IndexMapCore { /// indices mapping from the entry hash to its index. - indices: RawTable, - /// entries is a dense vec of entries in their order. - entries: Vec>, + indices: Indices, + /// entries is a dense vec maintaining entry order. + entries: Entries, +} + +/// Mutable references to the parts of an `IndexMapCore`. +/// +/// When using `HashTable::find_entry`, that takes hold of `&mut indices`, so we have to borrow our +/// `&mut entries` separately, and there's no way to go back to a `&mut IndexMapCore`. So this type +/// is used to implement methods on the split references, and `IndexMapCore` can also call those to +/// avoid duplication. +struct RefMut<'a, K, V> { + indices: &'a mut Indices, + entries: &'a mut Entries, } #[inline(always)] @@ -46,19 +60,33 @@ fn equivalent<'a, K, V, Q: ?Sized + Equivalent>( } #[inline] -fn erase_index(table: &mut RawTable, hash: HashValue, index: usize) { - let erased = table.erase_entry(hash.get(), move |&i| i == index); - debug_assert!(erased); +fn erase_index(table: &mut Indices, hash: HashValue, index: usize) { + if let Ok(entry) = table.find_entry(hash.get(), move |&i| i == index) { + entry.remove(); + } else if cfg!(debug_assertions) { + panic!("index not found"); + } } #[inline] -fn update_index(table: &mut RawTable, hash: HashValue, old: usize, new: usize) { +fn update_index(table: &mut Indices, hash: HashValue, old: usize, new: usize) { let index = table - .get_mut(hash.get(), move |&i| i == old) + .find_mut(hash.get(), move |&i| i == old) .expect("index not found"); *index = new; } +/// Inserts many entries into the indices table without reallocating, +/// and without regard for duplication. +/// +/// ***Panics*** if there is not sufficient capacity already. +fn insert_bulk_no_grow(indices: &mut Indices, entries: &[Bucket]) { + assert!(indices.capacity() - indices.len() >= entries.len()); + for entry in entries { + indices.insert_unique(entry.hash.get(), indices.len(), |_| unreachable!()); + } +} + impl Clone for IndexMapCore where K: Clone, @@ -71,32 +99,17 @@ where } fn clone_from(&mut self, other: &Self) { - let hasher = get_hash(&other.entries); - self.indices.clone_from_with_hasher(&other.indices, hasher); + self.indices.clone_from(&other.indices); if self.entries.capacity() < other.entries.len() { // If we must resize, match the indices capacity. let additional = other.entries.len() - self.entries.len(); - self.reserve_entries(additional); + self.borrow_mut().reserve_entries(additional); } self.entries.clone_from(&other.entries); } } -#[cfg(feature = "test_debug")] -impl core::fmt::Debug for IndexMapCore -where - K: core::fmt::Debug, - V: core::fmt::Debug, -{ - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("IndexMapCore") - .field("indices", &raw::DebugIndices(&self.indices)) - .field("entries", &self.entries) - .finish() - } -} - -impl Entries for IndexMapCore { +impl crate::Entries for IndexMapCore { type Entry = Bucket; #[inline] @@ -130,15 +143,20 @@ impl IndexMapCore { #[inline] pub(crate) const fn new() -> Self { IndexMapCore { - indices: RawTable::new(), + indices: Indices::new(), entries: Vec::new(), } } + #[inline] + fn borrow_mut(&mut self) -> RefMut<'_, K, V> { + RefMut::new(&mut self.indices, &mut self.entries) + } + #[inline] pub(crate) fn with_capacity(n: usize) -> Self { IndexMapCore { - indices: RawTable::with_capacity(n), + indices: Indices::with_capacity(n), entries: Vec::with_capacity(n), } } @@ -192,8 +210,8 @@ impl IndexMapCore { self.erase_indices(at, self.entries.len()); let entries = self.entries.split_off(at); - let mut indices = RawTable::with_capacity(entries.len()); - raw::insert_bulk_no_grow(&mut indices, &entries); + let mut indices = Indices::with_capacity(entries.len()); + insert_bulk_no_grow(&mut indices, &entries); Self { indices, entries } } @@ -206,15 +224,15 @@ impl IndexMapCore { let entries = self.entries.split_off(range.end); let drained = self.entries.split_off(range.start); - let mut indices = RawTable::with_capacity(entries.len()); - raw::insert_bulk_no_grow(&mut indices, &entries); + let mut indices = Indices::with_capacity(entries.len()); + insert_bulk_no_grow(&mut indices, &entries); (Self { indices, entries }, drained.into_iter()) } /// Append from another map without checking whether items already exist. pub(crate) fn append_unchecked(&mut self, other: &mut Self) { self.reserve(other.len()); - raw::insert_bulk_no_grow(&mut self.indices, &other.entries); + insert_bulk_no_grow(&mut self.indices, &other.entries); self.entries.append(&mut other.entries); other.indices.clear(); } @@ -224,20 +242,8 @@ impl IndexMapCore { self.indices.reserve(additional, get_hash(&self.entries)); // Only grow entries if necessary, since we also round up capacity. if additional > self.entries.capacity() - self.entries.len() { - self.reserve_entries(additional); - } - } - - /// Reserve entries capacity, rounded up to match the indices - fn reserve_entries(&mut self, additional: usize) { - // Use a soft-limit on the maximum capacity, but if the caller explicitly - // requested more, do it and let them have the resulting panic. - let new_capacity = Ord::min(self.indices.capacity(), Self::MAX_ENTRIES_CAPACITY); - let try_add = new_capacity - self.entries.len(); - if try_add > additional && self.entries.try_reserve_exact(try_add).is_ok() { - return; + self.borrow_mut().reserve_entries(additional); } - self.entries.reserve_exact(additional); } /// Reserve capacity for `additional` more key-value pairs, without over-allocating. @@ -301,45 +307,31 @@ impl IndexMapCore { } } - /// Append a key-value pair to `entries`, *without* checking whether it already exists. - fn push_entry(&mut self, 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::push` just double it. - self.reserve_entries(1); - } - 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 Q: ?Sized + Equivalent, { let eq = equivalent(key, &self.entries); - self.indices.get(hash.get(), eq).copied() + self.indices.find(hash.get(), eq).copied() } pub(crate) fn insert_full(&mut self, hash: HashValue, key: K, value: V) -> (usize, Option) where K: Eq, { - match self.find_or_insert(hash, &key) { - Ok(i) => (i, Some(mem::replace(&mut self.entries[i].value, value))), - Err(i) => { - debug_assert_eq!(i, self.entries.len()); - self.push_entry(hash, key, value); + let eq = equivalent(&key, &self.entries); + let hasher = get_hash(&self.entries); + match self.indices.entry(hash.get(), eq, hasher) { + hash_table::Entry::Occupied(entry) => { + let i = *entry.get(); + (i, Some(mem::replace(&mut self.entries[i].value, value))) + } + hash_table::Entry::Vacant(entry) => { + let i = self.entries.len(); + entry.insert(i); + self.borrow_mut().push_entry(hash, key, value); + debug_assert_eq!(self.indices.len(), self.entries.len()); (i, None) } } @@ -355,8 +347,11 @@ impl IndexMapCore { where K: Eq, { - match self.find_or_insert(hash, &key) { - Ok(i) => { + let eq = equivalent(&key, &self.entries); + let hasher = get_hash(&self.entries); + match self.indices.entry(hash.get(), eq, hasher) { + hash_table::Entry::Occupied(entry) => { + let i = *entry.get(); let entry = &mut self.entries[i]; let kv = ( mem::replace(&mut entry.key, key), @@ -364,17 +359,195 @@ impl IndexMapCore { ); (i, Some(kv)) } - Err(i) => { - debug_assert_eq!(i, self.entries.len()); - self.push_entry(hash, key, value); + hash_table::Entry::Vacant(entry) => { + let i = self.entries.len(); + entry.insert(i); + self.borrow_mut().push_entry(hash, key, value); + debug_assert_eq!(self.indices.len(), self.entries.len()); (i, None) } } } + /// 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 + Q: ?Sized + Equivalent, + { + let eq = equivalent(key, &self.entries); + match self.indices.find_entry(hash.get(), eq) { + Ok(entry) => { + let (index, _) = entry.remove(); + let (key, value) = self.borrow_mut().shift_remove_finish(index); + Some((index, key, value)) + } + Err(_) => None, + } + } + + /// Remove an entry by shifting all entries that follow it + #[inline] + pub(crate) fn shift_remove_index(&mut self, index: usize) -> Option<(K, V)> { + self.borrow_mut().shift_remove_index(index) + } + + #[inline] + pub(super) fn move_index(&mut self, from: usize, to: usize) { + self.borrow_mut().move_index(from, to); + } + + #[inline] + pub(crate) fn swap_indices(&mut self, a: usize, b: usize) { + self.borrow_mut().swap_indices(a, b); + } + + /// Remove an entry by swapping it with the last + pub(crate) fn swap_remove_full(&mut self, hash: HashValue, key: &Q) -> Option<(usize, K, V)> + where + Q: ?Sized + Equivalent, + { + let eq = equivalent(key, &self.entries); + match self.indices.find_entry(hash.get(), eq) { + Ok(entry) => { + let (index, _) = entry.remove(); + let (key, value) = self.borrow_mut().swap_remove_finish(index); + Some((index, key, value)) + } + Err(_) => None, + } + } + + /// Remove an entry by swapping it with the last + #[inline] + pub(crate) fn swap_remove_index(&mut self, index: usize) -> Option<(K, V)> { + self.borrow_mut().swap_remove_index(index) + } + + /// Erase `start..end` from `indices`, and shift `end..` indices down to `start..` + /// + /// All of these items should still be at their original location in `entries`. + /// This is used by `drain`, which will let `Vec::drain` do the work on `entries`. + fn erase_indices(&mut self, start: usize, end: usize) { + let (init, shifted_entries) = self.entries.split_at(end); + let (start_entries, erased_entries) = init.split_at(start); + + let erased = erased_entries.len(); + let shifted = shifted_entries.len(); + let half_capacity = self.indices.capacity() / 2; + + // Use a heuristic between different strategies + if erased == 0 { + // Degenerate case, nothing to do + } else if start + shifted < half_capacity && start < erased { + // Reinsert everything, as there are few kept indices + self.indices.clear(); + + // Reinsert stable indices, then shifted indices + insert_bulk_no_grow(&mut self.indices, start_entries); + insert_bulk_no_grow(&mut self.indices, shifted_entries); + } else if erased + shifted < half_capacity { + // Find each affected index, as there are few to adjust + + // Find erased indices + for (i, entry) in (start..).zip(erased_entries) { + erase_index(&mut self.indices, entry.hash, i); + } + + // Find shifted indices + for ((new, old), entry) in (start..).zip(end..).zip(shifted_entries) { + update_index(&mut self.indices, entry.hash, old, new); + } + } else { + // Sweep the whole table for adjustments + let offset = end - start; + self.indices.retain(move |i| { + if *i >= end { + *i -= offset; + true + } else { + *i < start + } + }); + } + + debug_assert_eq!(self.indices.len(), start + shifted); + } + + pub(crate) fn retain_in_order(&mut self, mut keep: F) + where + F: FnMut(&mut K, &mut V) -> bool, + { + self.entries + .retain_mut(|entry| keep(&mut entry.key, &mut entry.value)); + if self.entries.len() < self.indices.len() { + self.rebuild_hash_table(); + } + } + + fn rebuild_hash_table(&mut self) { + self.indices.clear(); + insert_bulk_no_grow(&mut self.indices, &self.entries); + } + + pub(crate) fn reverse(&mut self) { + self.entries.reverse(); + + // No need to save hash indices, can easily calculate what they should + // be, given that this is an in-place reversal. + let len = self.entries.len(); + for i in &mut self.indices { + *i = len - *i - 1; + } + } +} + +impl<'a, K, V> RefMut<'a, K, V> { + #[inline] + fn new(indices: &'a mut Indices, entries: &'a mut Entries) -> Self { + Self { indices, entries } + } + + /// Reserve entries capacity, rounded up to match the indices + fn reserve_entries(&mut self, additional: usize) { + // Use a soft-limit on the maximum capacity, but if the caller explicitly + // requested more, do it and let them have the resulting panic. + let new_capacity = Ord::min( + self.indices.capacity(), + IndexMapCore::::MAX_ENTRIES_CAPACITY, + ); + let try_add = new_capacity - self.entries.len(); + if try_add > additional && self.entries.try_reserve_exact(try_add).is_ok() { + return; + } + self.entries.reserve_exact(additional); + } + + /// Append a key-value pair to `entries`, + /// *without* checking whether it already exists. + fn push_entry(&mut self, 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::push` just double it. + self.reserve_entries(1); + } + 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 }); + } + 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)); + self.indices + .insert_unique(hash.get(), i, get_hash(self.entries)); debug_assert_eq!(i, self.entries.len()); self.push_entry(hash, key, value); i @@ -386,7 +559,7 @@ impl IndexMapCore { // 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| { + self.indices.insert_unique(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 }; @@ -396,25 +569,10 @@ impl IndexMapCore { } /// 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 - Q: ?Sized + Equivalent, - { - let eq = equivalent(key, &self.entries); - match self.indices.remove_entry(hash.get(), eq) { - Some(index) => { - let (key, value) = self.shift_remove_finish(index); - Some((index, key, value)) - } - None => None, - } - } - - /// Remove an entry by shifting all entries that follow it - pub(crate) fn shift_remove_index(&mut self, index: usize) -> Option<(K, V)> { + fn shift_remove_index(&mut self, index: usize) -> Option<(K, V)> { match self.entries.get(index) { Some(entry) => { - erase_index(&mut self.indices, entry.hash, index); + erase_index(self.indices, entry.hash, index); Some(self.shift_remove_finish(index)) } None => None, @@ -433,6 +591,36 @@ impl IndexMapCore { (entry.key, entry.value) } + /// Remove an entry by swapping it with the last + fn swap_remove_index(&mut self, index: usize) -> Option<(K, V)> { + match self.entries.get(index) { + Some(entry) => { + erase_index(self.indices, entry.hash, index); + Some(self.swap_remove_finish(index)) + } + None => None, + } + } + + /// Finish removing an entry by swapping it with the last + /// + /// The index should already be removed from `self.indices`. + fn swap_remove_finish(&mut self, index: usize) -> (K, V) { + // use swap_remove, but then we need to update the index that points + // to the other entry that has to move + let entry = self.entries.swap_remove(index); + + // correct index that points to the entry that had to swap places + if let Some(entry) = self.entries.get(index) { + // was not last element + // examine new element in `index` and find it in indices + let last = self.entries.len(); + update_index(self.indices, entry.hash, last, index); + } + + (entry.key, entry.value) + } + /// Decrement all indices in the range `start..end`. /// /// The index `start - 1` should not exist in `self.indices`. @@ -440,9 +628,9 @@ impl IndexMapCore { 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 { + if shifted_entries.len() > self.indices.capacity() / 2 { // Shift all indices in range. - for i in self.indices_mut() { + for i in &mut *self.indices { if start <= *i && *i < end { *i -= 1; } @@ -450,7 +638,7 @@ impl IndexMapCore { } else { // 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); + update_index(self.indices, entry.hash, i, i - 1); } } } @@ -462,9 +650,9 @@ impl IndexMapCore { 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 { + if shifted_entries.len() > self.indices.capacity() / 2 { // Shift all indices in range. - for i in self.indices_mut() { + for i in &mut *self.indices { if start <= *i && *i < end { *i += 1; } @@ -473,17 +661,17 @@ impl IndexMapCore { // 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); + update_index(self.indices, entry.hash, i, i + 1); } } } - pub(super) fn move_index(&mut self, from: usize, to: usize) { + fn move_index(&mut self, from: usize, to: usize) { let from_hash = self.entries[from].hash; let _ = self.entries[to]; // explicit bounds check if from != to { // Use a sentinel index so other indices don't collide. - update_index(&mut self.indices, from_hash, from, usize::MAX); + update_index(self.indices, from_hash, from, usize::MAX); // Update all other indices and rotate the entry positions. if from < to { @@ -495,141 +683,27 @@ impl IndexMapCore { } // Change the sentinel index to its final position. - update_index(&mut self.indices, from_hash, usize::MAX, to); + update_index(self.indices, from_hash, usize::MAX, to); } } - pub(crate) fn swap_indices(&mut self, a: usize, b: usize) { + fn swap_indices(&mut self, a: usize, b: usize) { // If they're equal and in-bounds, there's nothing to do. if a == b && a < self.entries.len() { return; } - // We'll get a "nice" bounds-check from indexing `self.entries`, + // We'll get a "nice" bounds-check from indexing `entries`, // and then we expect to find it in the table as well. - let [ref_a, ref_b] = self - .indices - .get_many_mut( - [self.entries[a].hash.get(), self.entries[b].hash.get()], - move |i, &x| if i == 0 { x == a } else { x == b }, - ) - .expect("indices not found"); - - mem::swap(ref_a, ref_b); - self.entries.swap(a, b); - } - - /// Remove an entry by swapping it with the last - pub(crate) fn swap_remove_full(&mut self, hash: HashValue, key: &Q) -> Option<(usize, K, V)> - where - Q: ?Sized + Equivalent, - { - let eq = equivalent(key, &self.entries); - match self.indices.remove_entry(hash.get(), eq) { - Some(index) => { - let (key, value) = self.swap_remove_finish(index); - Some((index, key, value)) + match self.indices.get_many_mut( + [self.entries[a].hash.get(), self.entries[b].hash.get()], + move |i, &x| if i == 0 { x == a } else { x == b }, + ) { + [Some(ref_a), Some(ref_b)] => { + mem::swap(ref_a, ref_b); + self.entries.swap(a, b); } - None => None, - } - } - - /// Remove an entry by swapping it with the last - pub(crate) fn swap_remove_index(&mut self, index: usize) -> Option<(K, V)> { - match self.entries.get(index) { - Some(entry) => { - erase_index(&mut self.indices, entry.hash, index); - Some(self.swap_remove_finish(index)) - } - None => None, - } - } - - /// Finish removing an entry by swapping it with the last - /// - /// The index should already be removed from `self.indices`. - fn swap_remove_finish(&mut self, index: usize) -> (K, V) { - // use swap_remove, but then we need to update the index that points - // to the other entry that has to move - let entry = self.entries.swap_remove(index); - - // correct index that points to the entry that had to swap places - if let Some(entry) = self.entries.get(index) { - // was not last element - // examine new element in `index` and find it in indices - let last = self.entries.len(); - update_index(&mut self.indices, entry.hash, last, index); - } - - (entry.key, entry.value) - } - - /// Erase `start..end` from `indices`, and shift `end..` indices down to `start..` - /// - /// All of these items should still be at their original location in `entries`. - /// This is used by `drain`, which will let `Vec::drain` do the work on `entries`. - fn erase_indices(&mut self, start: usize, end: usize) { - let (init, shifted_entries) = self.entries.split_at(end); - let (start_entries, erased_entries) = init.split_at(start); - - let erased = erased_entries.len(); - let shifted = shifted_entries.len(); - let half_capacity = self.indices.buckets() / 2; - - // Use a heuristic between different strategies - if erased == 0 { - // Degenerate case, nothing to do - } else if start + shifted < half_capacity && start < erased { - // Reinsert everything, as there are few kept indices - self.indices.clear(); - - // Reinsert stable indices, then shifted indices - raw::insert_bulk_no_grow(&mut self.indices, start_entries); - raw::insert_bulk_no_grow(&mut self.indices, shifted_entries); - } else if erased + shifted < half_capacity { - // Find each affected index, as there are few to adjust - - // Find erased indices - for (i, entry) in (start..).zip(erased_entries) { - erase_index(&mut self.indices, entry.hash, i); - } - - // Find shifted indices - for ((new, old), entry) in (start..).zip(end..).zip(shifted_entries) { - update_index(&mut self.indices, entry.hash, old, new); - } - } else { - // Sweep the whole table for adjustments - self.erase_indices_sweep(start, end); - } - - debug_assert_eq!(self.indices.len(), start + shifted); - } - - pub(crate) fn retain_in_order(&mut self, mut keep: F) - where - F: FnMut(&mut K, &mut V) -> bool, - { - self.entries - .retain_mut(|entry| keep(&mut entry.key, &mut entry.value)); - if self.entries.len() < self.indices.len() { - self.rebuild_hash_table(); - } - } - - fn rebuild_hash_table(&mut self) { - self.indices.clear(); - raw::insert_bulk_no_grow(&mut self.indices, &self.entries); - } - - pub(crate) fn reverse(&mut self) { - self.entries.reverse(); - - // No need to save hash indices, can easily calculate what they should - // be, given that this is an in-place reversal. - let len = self.entries.len(); - for i in self.indices_mut() { - *i = len - *i - 1; + _ => panic!("indices not found"), } } } @@ -640,4 +714,5 @@ fn assert_send_sync() { assert_send_sync::>(); assert_send_sync::>(); assert_send_sync::>(); + assert_send_sync::>(); } diff --git a/src/map/core/entry.rs b/src/map/core/entry.rs index 5ac8c495..f8a81367 100644 --- a/src/map/core/entry.rs +++ b/src/map/core/entry.rs @@ -1,16 +1,22 @@ -use super::raw::RawTableEntry; -use super::IndexMapCore; +use super::{equivalent, Entries, IndexMapCore, RefMut}; use crate::HashValue; use core::{fmt, mem}; +use hashbrown::hash_table; impl IndexMapCore { pub(crate) fn entry(&mut self, hash: HashValue, key: K) -> Entry<'_, K, V> where K: Eq, { - match self.raw_entry(hash, |k| *k == key) { - Ok(raw) => Entry::Occupied(OccupiedEntry { raw }), - Err(map) => Entry::Vacant(VacantEntry { map, hash, key }), + let entries = &mut self.entries; + let eq = equivalent(&key, entries); + match self.indices.find_entry(hash.get(), eq) { + Ok(index) => Entry::Occupied(OccupiedEntry { entries, index }), + Err(absent) => Entry::Vacant(VacantEntry { + map: RefMut::new(absent.into_table(), entries), + hash, + key, + }), } } } @@ -125,14 +131,20 @@ impl fmt::Debug for Entry<'_, K, V> { /// A view into an occupied entry in an [`IndexMap`][crate::IndexMap]. /// It is part of the [`Entry`] enum. pub struct OccupiedEntry<'a, K, V> { - raw: RawTableEntry<'a, K, V>, + entries: &'a mut Entries, + index: hash_table::OccupiedEntry<'a, usize>, } impl<'a, K, V> OccupiedEntry<'a, K, V> { /// Return the index of the key-value pair #[inline] pub fn index(&self) -> usize { - self.raw.index() + *self.index.get() + } + + #[inline] + fn into_ref_mut(self) -> RefMut<'a, K, V> { + RefMut::new(self.index.into_table(), self.entries) } /// Gets a reference to the entry's key in the map. @@ -141,16 +153,17 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { /// difference if the key type has any distinguishing features outside of `Hash` and `Eq`, like /// extra fields or the memory address of an allocation. pub fn key(&self) -> &K { - &self.raw.bucket().key + &self.entries[self.index()].key } pub(crate) fn key_mut(&mut self) -> &mut K { - &mut self.raw.bucket_mut().key + let index = self.index(); + &mut self.entries[index].key } /// Gets a reference to the entry's value in the map. pub fn get(&self) -> &V { - &self.raw.bucket().value + &self.entries[self.index()].value } /// Gets a mutable reference to the entry's value in the map. @@ -158,13 +171,15 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { /// If you need a reference which may outlive the destruction of the /// [`Entry`] value, see [`into_mut`][Self::into_mut]. pub fn get_mut(&mut self) -> &mut V { - &mut self.raw.bucket_mut().value + let index = self.index(); + &mut self.entries[index].value } /// Converts into a mutable reference to the entry's value in the map, /// with a lifetime bound to the map itself. pub fn into_mut(self) -> &'a mut V { - &mut self.raw.into_bucket().value + let index = self.index(); + &mut self.entries[index].value } /// Sets the value of the entry to `value`, and returns the entry's old value. @@ -226,8 +241,8 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { /// /// Computes in **O(1)** time (average). pub fn swap_remove_entry(self) -> (K, V) { - let (map, index) = self.raw.remove_index(); - map.swap_remove_finish(index) + let (index, entry) = self.index.remove(); + RefMut::new(entry.into_table(), self.entries).swap_remove_finish(index) } /// Remove and return the key, value pair stored in the map for this entry @@ -238,8 +253,8 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { /// /// Computes in **O(n)** time (average). pub fn shift_remove_entry(self) -> (K, V) { - let (map, index) = self.raw.remove_index(); - map.shift_remove_finish(index) + let (index, entry) = self.index.remove(); + RefMut::new(entry.into_table(), self.entries).shift_remove_finish(index) } /// Moves the position of the entry to a new index @@ -255,8 +270,8 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { /// /// 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); + let index = self.index(); + self.into_ref_mut().move_index(index, to); } /// Swaps the position of entry with another. @@ -268,8 +283,8 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { /// /// 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) + let index = self.index(); + self.into_ref_mut().swap_indices(index, other); } } @@ -283,11 +298,16 @@ 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 { + fn from(other: IndexedEntry<'a, K, V>) -> Self { + let IndexedEntry { + map: RefMut { indices, entries }, + index, + } = other; + let hash = entries[index].hash; Self { - raw: entry - .map - .index_raw_entry(entry.index) + entries, + index: indices + .find_entry(hash.get(), move |&i| i == index) .expect("index not found"), } } @@ -296,7 +316,7 @@ impl<'a, K, V> From> for OccupiedEntry<'a, K, V> { /// A view into a vacant entry in an [`IndexMap`][crate::IndexMap]. /// It is part of the [`Entry`] enum. pub struct VacantEntry<'a, K, V> { - map: &'a mut IndexMapCore, + map: RefMut<'a, K, V>, hash: HashValue, key: K, } @@ -323,10 +343,9 @@ 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 Self { map, hash, key } = self; - let i = map.insert_unique(hash, key, value); - &mut map.entries[i].value + pub fn insert(mut self, value: V) -> &'a mut V { + let i = self.map.insert_unique(self.hash, self.key, value); + &mut self.map.entries[i].value } /// Inserts the entry's key and the given value into the map at its ordered @@ -342,7 +361,7 @@ impl<'a, K, V> VacantEntry<'a, K, V> { where K: Ord, { - let slice = crate::map::Slice::from_slice(&self.map.entries); + let slice = crate::map::Slice::from_slice(self.map.entries); let i = slice.binary_search_keys(&self.key).unwrap_err(); (i, self.shift_insert(i, value)) } @@ -353,10 +372,10 @@ impl<'a, K, V> VacantEntry<'a, K, V> { /// ***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 + pub fn shift_insert(mut self, index: usize, value: V) -> &'a mut V { + self.map + .shift_insert_unique(index, self.hash, self.key, value); + &mut self.map.entries[index].value } } @@ -370,7 +389,7 @@ impl fmt::Debug for VacantEntry<'_, K, V> { /// /// This `struct` is created from the [`get_index_entry`][crate::IndexMap::get_index_entry] method. pub struct IndexedEntry<'a, K, V> { - map: &'a mut IndexMapCore, + map: RefMut<'a, K, V>, // We have a mutable reference to the map, which keeps the index // valid and pointing to the correct entry. index: usize, @@ -378,7 +397,10 @@ pub struct IndexedEntry<'a, K, V> { impl<'a, K, V> IndexedEntry<'a, K, V> { pub(crate) fn new(map: &'a mut IndexMapCore, index: usize) -> Self { - Self { map, index } + Self { + map: map.borrow_mut(), + index, + } } /// Return the index of the key-value pair @@ -427,7 +449,7 @@ impl<'a, K, V> IndexedEntry<'a, K, V> { /// **This perturbs the position of what used to be the last element!** /// /// Computes in **O(1)** time (average). - pub fn swap_remove_entry(self) -> (K, V) { + pub fn swap_remove_entry(mut self) -> (K, V) { self.map.swap_remove_index(self.index).unwrap() } @@ -438,7 +460,7 @@ impl<'a, K, V> IndexedEntry<'a, K, V> { /// **This perturbs the index of all of those elements!** /// /// Computes in **O(n)** time (average). - pub fn shift_remove_entry(self) -> (K, V) { + pub fn shift_remove_entry(mut self) -> (K, V) { self.map.shift_remove_index(self.index).unwrap() } @@ -476,7 +498,7 @@ impl<'a, K, V> IndexedEntry<'a, K, V> { /// ***Panics*** if `to` is out of bounds. /// /// Computes in **O(n)** time (average). - pub fn move_index(self, to: usize) { + pub fn move_index(mut self, to: usize) { self.map.move_index(self.index, to); } @@ -488,8 +510,8 @@ impl<'a, K, V> IndexedEntry<'a, K, V> { /// ***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) + pub fn swap_indices(mut self, other: usize) { + self.map.swap_indices(self.index, other); } } @@ -504,8 +526,10 @@ impl fmt::Debug for IndexedEntry<'_, K, V> { } 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 } + fn from(other: OccupiedEntry<'a, K, V>) -> Self { + Self { + index: other.index(), + map: other.into_ref_mut(), + } } } diff --git a/src/map/core/raw.rs b/src/map/core/raw.rs deleted file mode 100644 index c6a7b696..00000000 --- a/src/map/core/raw.rs +++ /dev/null @@ -1,164 +0,0 @@ -#![allow(unsafe_code)] -//! This module encapsulates the `unsafe` access to `hashbrown::raw::RawTable`, -//! mostly in dealing with its bucket "pointers". - -use super::{equivalent, get_hash, Bucket, HashValue, IndexMapCore}; -use hashbrown::raw::RawTable; - -type RawBucket = hashbrown::raw::Bucket; - -/// Inserts many entries into a raw table without reallocating. -/// -/// ***Panics*** if there is not sufficient capacity already. -pub(super) fn insert_bulk_no_grow(indices: &mut RawTable, entries: &[Bucket]) { - assert!(indices.capacity() - indices.len() >= entries.len()); - for entry in entries { - // SAFETY: we asserted that sufficient capacity exists for all entries. - unsafe { - indices.insert_no_grow(entry.hash.get(), indices.len()); - } - } -} - -#[cfg(feature = "test_debug")] -pub(super) struct DebugIndices<'a>(pub &'a RawTable); - -#[cfg(feature = "test_debug")] -impl core::fmt::Debug for DebugIndices<'_> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - // SAFETY: we're not letting any of the buckets escape this function - let indices = unsafe { self.0.iter().map(|raw_bucket| *raw_bucket.as_ref()) }; - f.debug_list().entries(indices).finish() - } -} - -impl IndexMapCore { - /// Sweep the whole table to erase indices start..end - pub(super) fn erase_indices_sweep(&mut self, start: usize, end: usize) { - // SAFETY: we're not letting any of the buckets escape this function - unsafe { - let offset = end - start; - for bucket in self.indices.iter() { - let i = bucket.as_mut(); - if *i >= end { - *i -= offset; - } else if *i >= start { - self.indices.erase(bucket); - } - } - } - } - - /// Search for a key in the table and return `Ok(entry_index)` if found. - /// Otherwise, insert the key and return `Err(new_index)`. - /// - /// Note that hashbrown may resize the table to reserve space for insertion, - /// even before checking if it's already present, so this is somewhat biased - /// towards new items. - pub(crate) fn find_or_insert(&mut self, hash: HashValue, key: &K) -> Result - where - K: Eq, - { - let hash = hash.get(); - let eq = equivalent(key, &self.entries); - let hasher = get_hash(&self.entries); - // SAFETY: We're not mutating between find and read/insert. - unsafe { - match self.indices.find_or_find_insert_slot(hash, eq, hasher) { - Ok(raw_bucket) => Ok(*raw_bucket.as_ref()), - Err(slot) => { - let index = self.indices.len(); - self.indices.insert_in_slot(hash, slot, index); - Err(index) - } - } - } - } - - pub(super) fn raw_entry( - &mut self, - hash: HashValue, - mut is_match: impl FnMut(&K) -> bool, - ) -> Result, &mut Self> { - let entries = &*self.entries; - let eq = move |&i: &usize| is_match(&entries[i].key); - match self.indices.find(hash.get(), eq) { - // 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`. - unsafe { self.indices.iter().map(|bucket| bucket.as_mut()) } - } -} - -/// A view into an occupied raw entry in an `IndexMap`. -// SAFETY: The lifetime of the map reference also constrains the raw bucket, -// which is essentially a raw pointer into the map indices. -pub(super) struct RawTableEntry<'a, K, V> { - map: &'a mut IndexMapCore, - raw_bucket: RawBucket, -} - -// `hashbrown::raw::Bucket` is only `Send`, not `Sync`. -// SAFETY: `&self` only accesses the bucket to read it. -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 { - // SAFETY: we have `&mut map` keeping the bucket stable - unsafe { *self.raw_bucket.as_ref() } - } - - #[inline] - pub(super) fn bucket(&self) -> &Bucket { - &self.map.entries[self.index()] - } - - #[inline] - pub(super) fn bucket_mut(&mut self) -> &mut Bucket { - let index = self.index(); - &mut self.map.entries[index] - } - - #[inline] - pub(super) fn into_bucket(self) -> &'a mut Bucket { - let index = self.index(); - &mut self.map.entries[index] - } - - /// Remove the index from indices, leaving the actual entries to the caller. - pub(super) fn remove_index(self) -> (&'a mut IndexMapCore, usize) { - // SAFETY: This is safe because it can only happen once (self is consumed) - // and map.indices have not been modified since entry construction - 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. - #[inline] - 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 87e532d5..5d73469d 100644 --- a/src/map/core/raw_entry_v1.rs +++ b/src/map/core/raw_entry_v1.rs @@ -9,13 +9,13 @@ //! `hash_raw_entry` feature (or some replacement), matching *inherent* methods will be added to //! `IndexMap` without such an opt-in trait. -use super::raw::RawTableEntry; -use super::IndexMapCore; +use super::{Entries, RefMut}; use crate::{Equivalent, HashValue, IndexMap}; use core::fmt; use core::hash::{BuildHasher, Hash, Hasher}; use core::marker::PhantomData; use core::mem; +use hashbrown::hash_table; /// Opt-in access to the experimental raw entry API. /// @@ -245,7 +245,7 @@ impl<'a, K, V, S> RawEntryBuilder<'a, K, V, S> { let hash = HashValue(hash as usize); let entries = &*self.map.core.entries; let eq = move |&i: &usize| is_match(&entries[i].key); - self.map.core.indices.get(hash.get(), eq).copied() + self.map.core.indices.find(hash.get(), eq).copied() } } @@ -283,18 +283,20 @@ impl<'a, K, V, S> RawEntryBuilderMut<'a, K, V, S> { } /// Access an entry by hash. - pub fn from_hash(self, hash: u64, is_match: F) -> RawEntryMut<'a, K, V, S> + pub fn from_hash(self, hash: u64, mut is_match: F) -> RawEntryMut<'a, K, V, S> where F: FnMut(&K) -> bool, { - let hash = HashValue(hash as usize); - match self.map.core.raw_entry(hash, is_match) { - Ok(raw) => RawEntryMut::Occupied(RawOccupiedEntryMut { - raw, + let ref_entries = &*self.map.core.entries; + let eq = move |&i: &usize| is_match(&ref_entries[i].key); + match self.map.core.indices.find_entry(hash, eq) { + Ok(index) => RawEntryMut::Occupied(RawOccupiedEntryMut { + entries: &mut self.map.core.entries, + index, hash_builder: PhantomData, }), - Err(map) => RawEntryMut::Vacant(RawVacantEntryMut { - map, + Err(absent) => RawEntryMut::Vacant(RawVacantEntryMut { + map: RefMut::new(absent.into_table(), &mut self.map.core.entries), hash_builder: &self.map.hash_builder, }), } @@ -377,7 +379,8 @@ impl<'a, K, V, S> RawEntryMut<'a, K, V, S> { /// A raw view into an occupied entry in an [`IndexMap`]. /// It is part of the [`RawEntryMut`] enum. pub struct RawOccupiedEntryMut<'a, K, V, S> { - raw: RawTableEntry<'a, K, V>, + entries: &'a mut Entries, + index: hash_table::OccupiedEntry<'a, usize>, hash_builder: PhantomData<&'a S>, } @@ -394,7 +397,12 @@ impl<'a, K, V, S> RawOccupiedEntryMut<'a, K, V, S> { /// Return the index of the key-value pair #[inline] pub fn index(&self) -> usize { - self.raw.index() + *self.index.get() + } + + #[inline] + fn into_ref_mut(self) -> RefMut<'a, K, V> { + RefMut::new(self.index.into_table(), self.entries) } /// Gets a reference to the entry's key in the map. @@ -403,7 +411,7 @@ impl<'a, K, V, S> RawOccupiedEntryMut<'a, K, V, S> { /// difference if the key type has any distinguishing features outside of `Hash` and `Eq`, like /// extra fields or the memory address of an allocation. pub fn key(&self) -> &K { - &self.raw.bucket().key + &self.entries[self.index()].key } /// Gets a mutable reference to the entry's key in the map. @@ -412,7 +420,8 @@ impl<'a, K, V, S> RawOccupiedEntryMut<'a, K, V, S> { /// difference if the key type has any distinguishing features outside of `Hash` and `Eq`, like /// extra fields or the memory address of an allocation. pub fn key_mut(&mut self) -> &mut K { - &mut self.raw.bucket_mut().key + let index = self.index(); + &mut self.entries[index].key } /// Converts into a mutable reference to the entry's key in the map, @@ -422,12 +431,13 @@ impl<'a, K, V, S> RawOccupiedEntryMut<'a, K, V, S> { /// difference if the key type has any distinguishing features outside of `Hash` and `Eq`, like /// extra fields or the memory address of an allocation. pub fn into_key(self) -> &'a mut K { - &mut self.raw.into_bucket().key + let index = self.index(); + &mut self.entries[index].key } /// Gets a reference to the entry's value in the map. pub fn get(&self) -> &V { - &self.raw.bucket().value + &self.entries[self.index()].value } /// Gets a mutable reference to the entry's value in the map. @@ -435,29 +445,33 @@ impl<'a, K, V, S> RawOccupiedEntryMut<'a, K, V, S> { /// If you need a reference which may outlive the destruction of the /// [`RawEntryMut`] value, see [`into_mut`][Self::into_mut]. pub fn get_mut(&mut self) -> &mut V { - &mut self.raw.bucket_mut().value + let index = self.index(); + &mut self.entries[index].value } /// Converts into a mutable reference to the entry's value in the map, /// with a lifetime bound to the map itself. pub fn into_mut(self) -> &'a mut V { - &mut self.raw.into_bucket().value + let index = self.index(); + &mut self.entries[index].value } /// Gets a reference to the entry's key and value in the map. pub fn get_key_value(&self) -> (&K, &V) { - self.raw.bucket().refs() + self.entries[self.index()].refs() } /// Gets a reference to the entry's key and value in the map. pub fn get_key_value_mut(&mut self) -> (&mut K, &mut V) { - self.raw.bucket_mut().muts() + let index = self.index(); + self.entries[index].muts() } /// Converts into a mutable reference to the entry's key and value in the map, /// with a lifetime bound to the map itself. pub fn into_key_value_mut(self) -> (&'a mut K, &'a mut V) { - self.raw.into_bucket().muts() + let index = self.index(); + self.entries[index].muts() } /// Sets the value of the entry, and returns the entry's old value. @@ -524,8 +538,8 @@ impl<'a, K, V, S> RawOccupiedEntryMut<'a, K, V, S> { /// /// Computes in **O(1)** time (average). pub fn swap_remove_entry(self) -> (K, V) { - let (map, index) = self.raw.remove_index(); - map.swap_remove_finish(index) + let (index, entry) = self.index.remove(); + RefMut::new(entry.into_table(), self.entries).swap_remove_finish(index) } /// Remove and return the key, value pair stored in the map for this entry @@ -536,8 +550,8 @@ impl<'a, K, V, S> RawOccupiedEntryMut<'a, K, V, S> { /// /// Computes in **O(n)** time (average). pub fn shift_remove_entry(self) -> (K, V) { - let (map, index) = self.raw.remove_index(); - map.shift_remove_finish(index) + let (index, entry) = self.index.remove(); + RefMut::new(entry.into_table(), self.entries).shift_remove_finish(index) } /// Moves the position of the entry to a new index @@ -553,8 +567,8 @@ impl<'a, K, V, S> RawOccupiedEntryMut<'a, K, V, S> { /// /// 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); + let index = self.index(); + self.into_ref_mut().move_index(index, to); } /// Swaps the position of entry with another. @@ -566,15 +580,15 @@ impl<'a, K, V, S> RawOccupiedEntryMut<'a, K, V, S> { /// /// 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) + let index = self.index(); + self.into_ref_mut().swap_indices(index, other); } } /// A view into a vacant raw entry in an [`IndexMap`]. /// It is part of the [`RawEntryMut`] enum. pub struct RawVacantEntryMut<'a, K, V, S> { - map: &'a mut IndexMapCore, + map: RefMut<'a, K, V>, hash_builder: &'a S, } @@ -604,7 +618,7 @@ 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) { + pub fn insert_hashed_nocheck(mut self, hash: u64, key: K, value: V) -> (&'a mut K, &'a mut V) { let hash = HashValue(hash as usize); let i = self.map.insert_unique(hash, key, value); self.map.entries[i].muts() @@ -633,7 +647,7 @@ impl<'a, K, V, S> RawVacantEntryMut<'a, K, V, S> { /// /// Computes in **O(n)** time (average). pub fn shift_insert_hashed_nocheck( - self, + mut self, index: usize, hash: u64, key: K, diff --git a/src/map/tests.rs b/src/map/tests.rs index ca7e9d6c..9de9db1b 100644 --- a/src/map/tests.rs +++ b/src/map/tests.rs @@ -822,7 +822,7 @@ macro_rules! move_index_oob { let mut map: IndexMap = (0..10).map(|k| (k, ())).collect(); map.move_index($from, $to); } - } + }; } move_index_oob!(test_move_index_out_of_bounds_0_10, 0, 10); move_index_oob!(test_move_index_out_of_bounds_0_max, 0, usize::MAX); From 53400496f4a6a97ebdca9f9fb374d1d617cf9148 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 1 Oct 2024 14:55:07 -0700 Subject: [PATCH 222/236] Release 2.6.0 --- Cargo.toml | 2 +- RELEASES.md | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 52257db9..959ea4c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "indexmap" edition = "2021" -version = "2.5.0" +version = "2.6.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 bfe8ce83..7e1686b0 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,10 @@ # Releases +## 2.6.0 + +- Implemented `Clone` for `map::IntoIter` and `set::IntoIter`. +- Updated the `hashbrown` dependency to version 0.15. + ## 2.5.0 - Added an `insert_before` method to `IndexMap` and `IndexSet`, as an From bd0b4f7c8c7fd636c1c8d04c62d3daf944e9f8b4 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 1 Oct 2024 16:32:19 -0700 Subject: [PATCH 223/236] Add all release dates --- RELEASES.md | 124 ++++++++++++++++++++++++++-------------------------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 7e1686b0..b1f7a519 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,43 +1,43 @@ # Releases -## 2.6.0 +## 2.6.0 (2024-10-01) - Implemented `Clone` for `map::IntoIter` and `set::IntoIter`. - Updated the `hashbrown` dependency to version 0.15. -## 2.5.0 +## 2.5.0 (2024-08-30) - 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 +## 2.4.0 (2024-08-13) - Added methods `IndexMap::append` and `IndexSet::append`, moving all items from one map or set into another, and leaving the original capacity for reuse. -## 2.3.0 +## 2.3.0 (2024-07-31) - Added trait `MutableEntryKey` for opt-in mutable access to map entry keys. - Added method `MutableKeys::iter_mut2` for opt-in mutable iteration of map keys and values. -## 2.2.6 +## 2.2.6 (2024-03-22) - Added trait `MutableValues` for opt-in mutable access to set values. -## 2.2.5 +## 2.2.5 (2024-02-29) - Added optional `borsh` serialization support. -## 2.2.4 +## 2.2.4 (2024-02-28) - Added an `insert_sorted` method on `IndexMap`, `IndexSet`, and `VacantEntry`. - Avoid hashing for lookups in single-entry maps. - Limit preallocated memory in `serde` deserializers. -## 2.2.3 +## 2.2.3 (2024-02-11) - Added `move_index` and `swap_indices` methods to `IndexedEntry`, `OccupiedEntry`, and `RawOccupiedEntryMut`, functioning like the existing @@ -48,18 +48,18 @@ - Added `shift_insert` methods on `IndexMap` and `IndexSet` to insert a new entry at a particular index, or else move an existing entry there. -## 2.2.2 +## 2.2.2 (2024-01-31) - Added indexing methods to raw entries: `RawEntryBuilder::from_hash_full`, `RawEntryBuilder::index_from_hash`, and `RawEntryMut::index`. -## 2.2.1 +## 2.2.1 (2024-01-28) - Corrected the signature of `RawOccupiedEntryMut::into_key(self) -> &'a mut K`, This a breaking change from 2.2.0, but that version was published for less than a day and has now been yanked. -## 2.2.0 +## 2.2.0 (2024-01-28) - The new `IndexMap::get_index_entry` method finds an entry by its index for in-place manipulation. @@ -83,7 +83,7 @@ The deprecated methods will remain to guide drop-in replacements from `HashMap` and `HashSet` toward the prefixed methods. -## 2.1.0 +## 2.1.0 (2023-10-31) - Empty slices can now be created with `map::Slice::{new, new_mut}` and `set::Slice::new`. In addition, `Slice::new`, `len`, and `is_empty` are @@ -95,17 +95,17 @@ comparators, `binary_search_by_key` for key extraction, and `partition_point` for boolean conditions. -## 2.0.2 +## 2.0.2 (2023-09-29) - The `hashbrown` dependency has been updated to version 0.14.1 to complete the support for Rust 1.63. -## 2.0.1 +## 2.0.1 (2023-09-27) - **MSRV**: Rust 1.63.0 is now supported as well, pending publication of `hashbrown`'s relaxed MSRV (or use cargo `--ignore-rust-version`). -## 2.0.0 +## 2.0.0 (2023-06-23) - **MSRV**: Rust 1.64.0 or later is now required. @@ -141,22 +141,22 @@ - The `serde_seq` module has been moved from the crate root to below the `map` module. -## 1.9.3 +## 1.9.3 (2023-03-24) - Bump the `rustc-rayon` dependency, for compiler use only. -## 1.9.2 +## 1.9.2 (2022-11-17) - `IndexMap` and `IndexSet` both implement `arbitrary::Arbitrary<'_>` and `quickcheck::Arbitrary` if those optional dependency features are enabled. -## 1.9.1 +## 1.9.1 (2022-06-21) - The MSRV now allows Rust 1.56.0 as well. However, currently `hashbrown` 0.12.1 requires 1.56.1, so users on 1.56.0 should downgrade that to 0.12.0 until there is a later published version relaxing its requirement. -## 1.9.0 +## 1.9.0 (2022-06-16) - **MSRV**: Rust 1.56.1 or later is now required. @@ -171,18 +171,18 @@ the position of an item from one index to another, shifting the items between to accommodate the move. -## 1.8.2 +## 1.8.2 (2022-05-27) - Bump the `rustc-rayon` dependency, for compiler use only. -## 1.8.1 +## 1.8.1 (2022-03-29) - The new `IndexSet::replace_full` will return the index of the item along with the replaced value, if any, by @zakcutner in PR [222]. [222]: https://github.com/indexmap-rs/indexmap/pull/222 -## 1.8.0 +## 1.8.0 (2022-01-07) - The new `IndexMap::into_keys` and `IndexMap::into_values` will consume the map into keys or values, respectively, matching Rust 1.54's `HashMap` @@ -212,13 +212,13 @@ [205]: https://github.com/indexmap-rs/indexmap/pull/205 [211]: https://github.com/indexmap-rs/indexmap/pull/211 -## 1.7.0 +## 1.7.0 (2021-06-29) - **MSRV**: Rust 1.49 or later is now required. - The `hashbrown` dependency has been updated to version 0.11. -## 1.6.2 +## 1.6.2 (2021-03-05) - Fixed to match `std` behavior, `OccupiedEntry::key` now references the existing key in the map instead of the lookup key, by @cuviper in PR [170]. @@ -229,7 +229,7 @@ [170]: https://github.com/indexmap-rs/indexmap/pull/170 [175]: https://github.com/indexmap-rs/indexmap/pull/175 -## 1.6.1 +## 1.6.1 (2020-12-14) - The new `serde_seq` module implements `IndexMap` serialization as a sequence to ensure order is preserved, by @cuviper in PR [158]. @@ -241,13 +241,13 @@ [158]: https://github.com/indexmap-rs/indexmap/pull/158 [160]: https://github.com/indexmap-rs/indexmap/pull/160 -## 1.6.0 +## 1.6.0 (2020-09-05) - **MSRV**: Rust 1.36 or later is now required. - The `hashbrown` dependency has been updated to version 0.9. -## 1.5.2 +## 1.5.2 (2020-09-01) - The new "std" feature will force the use of `std` for users that explicitly want the default `S = RandomState`, bypassing the autodetection added in 1.3.0, @@ -255,7 +255,7 @@ [145]: https://github.com/indexmap-rs/indexmap/pull/145 -## 1.5.1 +## 1.5.1 (2020-08-07) - Values can now be indexed by their `usize` position by @cuviper in PR [132]. @@ -267,7 +267,7 @@ [141]: https://github.com/indexmap-rs/indexmap/pull/141 [142]: https://github.com/indexmap-rs/indexmap/pull/142 -## 1.5.0 +## 1.5.0 (2020-07-17) - **MSRV**: Rust 1.32 or later is now required. @@ -286,7 +286,7 @@ [131]: https://github.com/indexmap-rs/indexmap/pull/131 [136]: https://github.com/indexmap-rs/indexmap/pull/136 -## 1.4.0 +## 1.4.0 (2020-06-01) - Add new method `get_index_of` by @Thermatrix in PR [115] and [120] @@ -301,15 +301,15 @@ [115]: https://github.com/indexmap-rs/indexmap/pull/115 [120]: https://github.com/indexmap-rs/indexmap/pull/120 -## 1.3.2 +## 1.3.2 (2020-02-05) - Maintenance update to regenerate the published `Cargo.toml`. -## 1.3.1 +## 1.3.1 (2020-01-15) - Maintenance update for formatting and `autocfg` 1.0. -## 1.3.0 +## 1.3.0 (2019-10-18) - The deprecation messages in the previous version have been removed. (The methods have not otherwise changed.) Docs for removal methods have been @@ -319,7 +319,7 @@ `std` is not available. There is no crate feature to enable/disable to trigger this. The new build-dep `autocfg` enables this. -## 1.2.0 +## 1.2.0 (2019-09-08) - Plain `.remove()` now has a deprecation message, it informs the user about picking one of the removal functions `swap_remove` and `shift_remove` @@ -336,7 +336,7 @@ - Internal improvements: fix warnings, deprecations and style lints -## 1.1.0 +## 1.1.0 (2019-08-20) - Added optional feature `"rayon"` that adds parallel iterator support to `IndexMap` and `IndexSet` using Rayon. This includes all the regular @@ -352,7 +352,7 @@ - Minimum Rust version requirement increased to Rust 1.30 for development builds. -## 1.0.2 +## 1.0.2 (2018-10-22) - The new methods `IndexMap::insert_full` and `IndexSet::insert_full` are both like `insert` with the index included in the return value. @@ -363,11 +363,11 @@ - The new method `Entry::or_default` inserts a default value in unoccupied entries, matching the new methods of `std` maps in Rust 1.28. -## 1.0.1 +## 1.0.1 (2018-03-24) - Document Rust version policy for the crate (see rustdoc) -## 1.0.0 +## 1.0.0 (2018-03-11) - This is the 1.0 release for `indexmap`! (the crate and datastructure formerly known as “ordermap”) @@ -378,12 +378,12 @@ `OrderSet`, `ordermap!{}`, `orderset!{}`. Use the new `IndexMap` etc names instead. -## 0.4.1 +## 0.4.1 (2018-02-14) - Renamed crate to `indexmap`; the `ordermap` crate is now deprecated and the types `OrderMap/Set` now have a deprecation notice. -## 0.4.0 +## 0.4.0 (2018-02-02) - This is the last release series for this `ordermap` under that name, because the crate is **going to be renamed** to `indexmap` (with types @@ -402,11 +402,11 @@ - Minimum Rust version requirement increased to Rust 1.18 -## 0.3.5 +## 0.3.5 (2018-01-14) - Documentation improvements -## 0.3.4 +## 0.3.4 (2018-01-04) - The `.retain()` methods for `OrderMap` and `OrderSet` now traverse the elements in order, and the retained elements **keep their order** @@ -414,23 +414,23 @@ `.sort_by()`, `.sort()` to `OrderSet`. These methods allow you to sort the maps in place efficiently. -## 0.3.3 +## 0.3.3 (2017-12-28) - Document insertion behaviour better by @lucab - Updated dependences (no feature changes) by @ignatenkobrain -## 0.3.2 +## 0.3.2 (2017-11-25) - Add `OrderSet` by @cuviper! - `OrderMap::drain` is now (too) a double ended iterator. -## 0.3.1 +## 0.3.1 (2017-11-19) - In all ordermap iterators, forward the `collect` method to the underlying iterator as well. - Add crates.io categories. -## 0.3.0 +## 0.3.0 (2017-10-07) - The methods `get_pair`, `get_pair_index` were both replaced by `get_full` (and the same for the mutable case). @@ -445,71 +445,71 @@ [#10]: https://github.com/indexmap-rs/indexmap/pull/10 -## 0.2.13 +## 0.2.13 (2017-09-30) - Fix deserialization to support custom hashers by @Techcable. - Add methods `.index()` on the entry types by @garro95. -## 0.2.12 +## 0.2.12 (2017-09-11) - Add methods `.with_hasher()`, `.hasher()`. -## 0.2.11 +## 0.2.11 (2017-08-29) - Support `ExactSizeIterator` for the iterators. By @Binero. - Use `Box<[Pos]>` internally, saving a word in the `OrderMap` struct. - Serde support, with crate feature `"serde-1"`. By @xfix. -## 0.2.10 +## 0.2.10 (2017-04-29) - Add iterator `.drain(..)` by @stevej. -## 0.2.9 +## 0.2.9 (2017-03-26) - Add method `.is_empty()` by @overvenus. - Implement `PartialEq, Eq` by @overvenus. - Add method `.sorted_by()`. -## 0.2.8 +## 0.2.8 (2017-03-01) - Add iterators `.values()` and `.values_mut()`. - Fix compatibility with 32-bit platforms. -## 0.2.7 +## 0.2.7 (2016-11-02) - Add `.retain()`. -## 0.2.6 +## 0.2.6 (2016-11-02) - Add `OccupiedEntry::remove_entry` and other minor entry methods, so that it now has all the features of `HashMap`'s entries. -## 0.2.5 +## 0.2.5 (2016-10-31) - Improved `.pop()` slightly. -## 0.2.4 +## 0.2.4 (2016-10-22) - Improved performance of `.insert()` ([#3]) by @pczarn. [#3]: https://github.com/indexmap-rs/indexmap/pull/3 -## 0.2.3 +## 0.2.3 (2016-10-11) - Generalize `Entry` for now, so that it works on hashmaps with non-default hasher. However, there's a lingering compat issue since libstd `HashMap` does not parameterize its entries by the hasher (`S` typarm). - Special case some iterator methods like `.nth()`. -## 0.2.2 +## 0.2.2 (2016-10-02) - Disable the verbose `Debug` impl by default. -## 0.2.1 +## 0.2.1 (2016-10-02) - Fix doc links and clarify docs. -## 0.2.0 +## 0.2.0 (2016-10-01) - Add more `HashMap` methods & compat with its API. - Experimental support for `.entry()` (the simplest parts of the API). @@ -521,11 +521,11 @@ `.get_index()`, `.get_index_mut()`, `.swap_remove_index()`, `.get_pair_index()`, `.get_pair_index_mut()`. -## 0.1.2 +## 0.1.2 (2016-09-19) - Implement the 32/32 split idea for `Pos` which improves cache utilization and lookup performance. -## 0.1.1 +## 0.1.1 (2016-09-16) - Initial release. From 7d8cef8b4bd8f9295de4fd53317946a543b2b089 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 13 Nov 2024 15:39:12 -0800 Subject: [PATCH 224/236] Use rayon-1.9.0's `collect_vec_list` --- Cargo.toml | 2 +- src/rayon/mod.rs | 15 +-------------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 959ea4c6..9e22c312 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ arbitrary = { version = "1.0", optional = true, default-features = false } quickcheck = { version = "1.0", optional = true, default-features = false } serde = { version = "1.0", optional = true, default-features = false } borsh = { version = "1.2", optional = true, default-features = false } -rayon = { version = "1.5.3", optional = true } +rayon = { version = "1.9", optional = true } # Internal feature, only used when building as part of rustc, # not part of the stable interface of this crate. diff --git a/src/rayon/mod.rs b/src/rayon/mod.rs index 1d21569c..84ce02db 100644 --- a/src/rayon/mod.rs +++ b/src/rayon/mod.rs @@ -12,18 +12,5 @@ pub mod set; // This form of intermediate collection is also how Rayon collects `HashMap`. // Note that the order will also be preserved! fn collect(iter: I) -> LinkedList> { - iter.into_par_iter() - .fold(Vec::new, |mut vec, elem| { - vec.push(elem); - vec - }) - .map(|vec| { - let mut list = LinkedList::new(); - list.push_back(vec); - list - }) - .reduce(LinkedList::new, |mut list1, mut list2| { - list1.append(&mut list2); - list1 - }) + iter.into_par_iter().collect_vec_list() } From c095322249f85d36d46c2a8d4808f3e1c70ddb08 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 13 Nov 2024 15:57:47 -0800 Subject: [PATCH 225/236] ci: downgrade hashbrown for 1.63 --- .github/workflows/ci.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 98c77507..efcb2066 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,6 +48,11 @@ jobs: - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} + - name: Downgrade dependencies + if: matrix.rust == '1.63.0' + run: | + cargo generate-lockfile + cargo update -p hashbrown --precise 0.15.0 - name: Tests run: | cargo build --verbose --features "${{ matrix.features }}" @@ -83,6 +88,11 @@ jobs: with: toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} + - name: Downgrade dependencies + if: matrix.rust == '1.63.0' + run: | + cargo generate-lockfile + cargo update -p hashbrown --precise 0.15.0 - name: Tests run: | cargo build -vv --target=${{ matrix.target }} --no-default-features From 2a0ca974171768d3bfb388e1f7963bed0078c7c6 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 1 Oct 2024 15:59:01 -0700 Subject: [PATCH 226/236] Add `{Entry,VacantEntry}::insert_entry` --- src/map/core.rs | 50 +++++++++++++++++++----------------- src/map/core/entry.rs | 40 ++++++++++++++++++++++++++--- src/map/core/raw_entry_v1.rs | 5 ++-- 3 files changed, 66 insertions(+), 29 deletions(-) diff --git a/src/map/core.rs b/src/map/core.rs index f42cccbf..7cebb466 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -316,6 +316,17 @@ impl IndexMapCore { self.indices.find(hash.get(), eq).copied() } + /// Append a key-value pair to `entries`, + /// *without* checking whether it already exists. + fn push_entry(&mut self, 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::push` just double it. + self.borrow_mut().reserve_entries(1); + } + self.entries.push(Bucket { hash, key, value }); + } + pub(crate) fn insert_full(&mut self, hash: HashValue, key: K, value: V) -> (usize, Option) where K: Eq, @@ -330,7 +341,7 @@ impl IndexMapCore { hash_table::Entry::Vacant(entry) => { let i = self.entries.len(); entry.insert(i); - self.borrow_mut().push_entry(hash, key, value); + self.push_entry(hash, key, value); debug_assert_eq!(self.indices.len(), self.entries.len()); (i, None) } @@ -362,7 +373,7 @@ impl IndexMapCore { hash_table::Entry::Vacant(entry) => { let i = self.entries.len(); entry.insert(i); - self.borrow_mut().push_entry(hash, key, value); + self.push_entry(hash, key, value); debug_assert_eq!(self.indices.len(), self.entries.len()); (i, None) } @@ -522,37 +533,25 @@ impl<'a, K, V> RefMut<'a, K, V> { self.entries.reserve_exact(additional); } - /// Append a key-value pair to `entries`, + /// Insert a key-value pair in `entries`, /// *without* checking whether it already exists. - fn push_entry(&mut self, hash: HashValue, key: K, value: V) { + fn insert_unique(mut self, hash: HashValue, key: K, value: V) -> OccupiedEntry<'a, K, V> { if self.entries.len() == self.entries.capacity() { // Reserve our own capacity synced to the indices, // rather than letting `Vec::push` just double it. self.reserve_entries(1); } - 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 }); - } - - fn insert_unique(&mut self, hash: HashValue, key: K, value: V) -> usize { let i = self.indices.len(); - self.indices + let entry = self + .indices .insert_unique(hash.get(), i, get_hash(self.entries)); debug_assert_eq!(i, self.entries.len()); - self.push_entry(hash, key, value); - i + self.entries.push(Bucket { hash, key, value }); + OccupiedEntry::new(self.entries, entry) } + /// Insert a key-value pair in `entries` at a particular index, + /// *without* checking whether it already exists. fn shift_insert_unique(&mut self, index: usize, hash: HashValue, key: K, value: V) { let end = self.indices.len(); assert!(index <= end); @@ -565,7 +564,12 @@ impl<'a, K, V> RefMut<'a, K, V> { let i = if i < index { i } else { i - 1 }; entries[i].hash.get() }); - self.insert_entry(index, hash, key, value); + 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 }); } /// Remove an entry by shifting all entries that follow it diff --git a/src/map/core/entry.rs b/src/map/core/entry.rs index f8a81367..dafdf1b6 100644 --- a/src/map/core/entry.rs +++ b/src/map/core/entry.rs @@ -39,6 +39,19 @@ impl<'a, K, V> Entry<'a, K, V> { } } + /// Sets the value of the entry (after inserting if vacant), and returns an `OccupiedEntry`. + /// + /// Computes in **O(1)** time (amortized average). + pub fn insert_entry(self, value: V) -> OccupiedEntry<'a, K, V> { + match self { + Entry::Occupied(mut entry) => { + entry.insert(value); + entry + } + Entry::Vacant(entry) => entry.insert_entry(value), + } + } + /// Inserts the given default value in the entry if it is vacant and returns a mutable /// reference to it. Otherwise a mutable reference to an already existent value is returned. /// @@ -136,6 +149,13 @@ pub struct OccupiedEntry<'a, K, V> { } impl<'a, K, V> OccupiedEntry<'a, K, V> { + pub(crate) fn new( + entries: &'a mut Entries, + index: hash_table::OccupiedEntry<'a, usize>, + ) -> Self { + Self { entries, index } + } + /// Return the index of the key-value pair #[inline] pub fn index(&self) -> usize { @@ -182,6 +202,11 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { &mut self.entries[index].value } + pub(super) fn into_muts(self) -> (&'a mut K, &'a mut V) { + let index = self.index(); + self.entries[index].muts() + } + /// Sets the value of the entry to `value`, and returns the entry's old value. pub fn insert(&mut self, value: V) -> V { mem::replace(self.get_mut(), value) @@ -343,9 +368,18 @@ 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(mut self, value: V) -> &'a mut V { - let i = self.map.insert_unique(self.hash, self.key, value); - &mut self.map.entries[i].value + /// + /// Computes in **O(1)** time (amortized average). + pub fn insert(self, value: V) -> &'a mut V { + self.insert_entry(value).into_mut() + } + + /// Inserts the entry's key and the given value into the map, and returns an `OccupiedEntry`. + /// + /// Computes in **O(1)** time (amortized average). + pub fn insert_entry(self, value: V) -> OccupiedEntry<'a, K, V> { + let Self { map, hash, key } = self; + map.insert_unique(hash, key, value) } /// Inserts the entry's key and the given value into the map at its ordered diff --git a/src/map/core/raw_entry_v1.rs b/src/map/core/raw_entry_v1.rs index 5d73469d..757a3cee 100644 --- a/src/map/core/raw_entry_v1.rs +++ b/src/map/core/raw_entry_v1.rs @@ -618,10 +618,9 @@ 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(mut self, hash: u64, key: K, value: V) -> (&'a mut K, &'a mut V) { + pub fn insert_hashed_nocheck(self, hash: u64, key: K, value: V) -> (&'a mut K, &'a mut V) { let hash = HashValue(hash as usize); - let i = self.map.insert_unique(hash, key, value); - self.map.entries[i].muts() + self.map.insert_unique(hash, key, value).into_muts() } /// Inserts the given key and value into the map at the given index, From 998edb12fea0744828fd553a00d474b441f711bf Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sat, 30 Nov 2024 16:31:53 -0800 Subject: [PATCH 227/236] Release 2.7.0 --- Cargo.toml | 2 +- RELEASES.md | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9e22c312..d8536635 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "indexmap" edition = "2021" -version = "2.6.0" +version = "2.7.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 b1f7a519..befdd0e0 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,10 @@ # Releases +## 2.7.0 (2024-11-30) + +- Added methods `Entry::insert_entry` and `VacantEntry::insert_entry`, returning + an `OccupiedEntry` after insertion. + ## 2.6.0 (2024-10-01) - Implemented `Clone` for `map::IntoIter` and `set::IntoIter`. From 779505fb29b8d7dce6343ba06103532a6880b3a9 Mon Sep 17 00:00:00 2001 From: Fraser Hutchison Date: Mon, 16 Dec 2024 10:41:06 +0000 Subject: [PATCH 228/236] Mention cyclic dependency in main rustdocs --- src/lib.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 3e16bc6e..518b236f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,7 +36,11 @@ //! (de)serializing [`IndexMap`] as an ordered sequence are available in the //! [`map::serde_seq`] module. //! * `borsh`: Adds implementations for [`BorshSerialize`] and [`BorshDeserialize`] -//! to [`IndexMap`] and [`IndexSet`]. +//! to [`IndexMap`] and [`IndexSet`]. **Note:** When this feature is enabled, +//! you cannot enable the `derive` feature of [`borsh`] due to a cyclic +//! dependency. Instead, add the `borsh-derive` crate as an explicit +//! dependency in your Cargo.toml and import as e.g. +//! `use borsh_derive::{BorshSerialize, BorshDeserialize};`. //! * `arbitrary`: Adds implementations for the [`arbitrary::Arbitrary`] trait //! to [`IndexMap`] and [`IndexSet`]. //! * `quickcheck`: Adds implementations for the [`quickcheck::Arbitrary`] trait @@ -50,6 +54,7 @@ //! [`Deserialize`]: `::serde::Deserialize` //! [`BorshSerialize`]: `::borsh::BorshSerialize` //! [`BorshDeserialize`]: `::borsh::BorshDeserialize` +//! [`borsh`]: `::borsh` //! [`arbitrary::Arbitrary`]: `::arbitrary::Arbitrary` //! [`quickcheck::Arbitrary`]: `::quickcheck::Arbitrary` //! From 8eb4de59b03adf2e24284f6ca370d5071c493f9f Mon Sep 17 00:00:00 2001 From: Marijn Schouten Date: Mon, 30 Dec 2024 17:34:23 +0100 Subject: [PATCH 229/236] Remove fxhash example from lib.rs Remove fxhash example as fxhash is not maintained. --- src/lib.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 518b236f..4fd9aac1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,20 +68,14 @@ //! //! ``` //! use fnv::FnvBuildHasher; -//! use fxhash::FxBuildHasher; //! use indexmap::{IndexMap, IndexSet}; //! //! type FnvIndexMap = IndexMap; //! type FnvIndexSet = IndexSet; //! -//! type FxIndexMap = IndexMap; -//! type FxIndexSet = IndexSet; -//! //! let std: IndexSet = (0..100).collect(); //! let fnv: FnvIndexSet = (0..100).collect(); -//! let fx: FxIndexSet = (0..100).collect(); //! assert_eq!(std, fnv); -//! assert_eq!(std, fx); //! ``` //! //! ### Rust Version From f63bb6e016f2f78516441bc42e586828211ed2dc Mon Sep 17 00:00:00 2001 From: Marijn Schouten Date: Mon, 30 Dec 2024 17:35:13 +0100 Subject: [PATCH 230/236] remove fxhash dep --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d8536635..1ac50605 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,6 @@ rand = {version = "0.8", features = ["small_rng"] } quickcheck = { version = "1.0", default-features = false } fnv = "1.0" lazy_static = "1.3" -fxhash = "0.2.1" serde_derive = "1.0" [features] From 7b64edc994ec11867479f974f190e302e3e66563 Mon Sep 17 00:00:00 2001 From: savannstm Date: Mon, 13 Jan 2025 23:16:36 +0500 Subject: [PATCH 231/236] Add `#[track_caller]` attributes to functions that may panic --- src/map.rs | 7 +++++++ src/map/core.rs | 7 +++++++ src/map/core/entry.rs | 2 ++ src/map/iter.rs | 1 + src/set.rs | 7 +++++++ src/set/iter.rs | 1 + src/util.rs | 1 + 7 files changed, 26 insertions(+) diff --git a/src/map.rs b/src/map.rs index 946cb6fc..e19266a0 100644 --- a/src/map.rs +++ b/src/map.rs @@ -299,6 +299,7 @@ impl IndexMap { /// /// ***Panics*** if the starting point is greater than the end point or if /// the end point is greater than the length of the map. + #[track_caller] pub fn drain(&mut self, range: R) -> Drain<'_, K, V> where R: RangeBounds, @@ -313,6 +314,7 @@ impl IndexMap { /// the elements `[0, at)` with its previous capacity unchanged. /// /// ***Panics*** if `at > len`. + #[track_caller] pub fn split_off(&mut self, at: usize) -> Self where S: Clone, @@ -493,6 +495,7 @@ where /// assert_eq!(map.get_index_of(&'+'), Some(27)); /// assert_eq!(map.len(), 28); /// ``` + #[track_caller] 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) { @@ -571,6 +574,7 @@ where /// // This is an invalid index for moving an existing key! /// map.shift_insert(map.len(), 'a', ()); /// ``` + #[track_caller] pub fn shift_insert(&mut self, index: usize, key: K, value: V) -> Option { let len = self.len(); match self.entry(key) { @@ -627,6 +631,7 @@ where /// assert!(map.into_iter().eq([(0, '_'), (1, 'A'), (5, 'E'), (3, 'C'), (2, 'B'), (4, 'D')])); /// assert_eq!(removed, &[(2, 'b'), (3, 'c')]); /// ``` + #[track_caller] pub fn splice(&mut self, range: R, replace_with: I) -> Splice<'_, I::IntoIter, K, V, S> where R: RangeBounds, @@ -1278,6 +1283,7 @@ impl IndexMap { /// ***Panics*** if `from` or `to` are out of bounds. /// /// Computes in **O(n)** time (average). + #[track_caller] pub fn move_index(&mut self, from: usize, to: usize) { self.core.move_index(from, to) } @@ -1287,6 +1293,7 @@ impl IndexMap { /// ***Panics*** if `a` or `b` are out of bounds. /// /// Computes in **O(1)** time (average). + #[track_caller] 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 7cebb466..20f68343 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -183,6 +183,7 @@ impl IndexMapCore { } } + #[track_caller] pub(crate) fn drain(&mut self, range: R) -> vec::Drain<'_, Bucket> where R: RangeBounds, @@ -205,6 +206,7 @@ impl IndexMapCore { self.entries.par_drain(range) } + #[track_caller] pub(crate) fn split_off(&mut self, at: usize) -> Self { assert!(at <= self.entries.len()); self.erase_indices(at, self.entries.len()); @@ -215,6 +217,7 @@ impl IndexMapCore { Self { indices, entries } } + #[track_caller] pub(crate) fn split_splice(&mut self, range: R) -> (Self, vec::IntoIter>) where R: RangeBounds, @@ -403,11 +406,13 @@ impl IndexMapCore { } #[inline] + #[track_caller] pub(super) fn move_index(&mut self, from: usize, to: usize) { self.borrow_mut().move_index(from, to); } #[inline] + #[track_caller] pub(crate) fn swap_indices(&mut self, a: usize, b: usize) { self.borrow_mut().swap_indices(a, b); } @@ -670,6 +675,7 @@ impl<'a, K, V> RefMut<'a, K, V> { } } + #[track_caller] fn move_index(&mut self, from: usize, to: usize) { let from_hash = self.entries[from].hash; let _ = self.entries[to]; // explicit bounds check @@ -691,6 +697,7 @@ impl<'a, K, V> RefMut<'a, K, V> { } } + #[track_caller] fn swap_indices(&mut self, a: usize, b: usize) { // If they're equal and in-bounds, there's nothing to do. if a == b && a < self.entries.len() { diff --git a/src/map/core/entry.rs b/src/map/core/entry.rs index dafdf1b6..6ab29ca5 100644 --- a/src/map/core/entry.rs +++ b/src/map/core/entry.rs @@ -294,6 +294,7 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { /// ***Panics*** if `to` is out of bounds. /// /// Computes in **O(n)** time (average). + #[track_caller] pub fn move_index(self, to: usize) { let index = self.index(); self.into_ref_mut().move_index(index, to); @@ -532,6 +533,7 @@ impl<'a, K, V> IndexedEntry<'a, K, V> { /// ***Panics*** if `to` is out of bounds. /// /// Computes in **O(n)** time (average). + #[track_caller] pub fn move_index(mut self, to: usize) { self.map.move_index(self.index, to); } diff --git a/src/map/iter.rs b/src/map/iter.rs index 2943f18a..83d2e3f7 100644 --- a/src/map/iter.rs +++ b/src/map/iter.rs @@ -667,6 +667,7 @@ where K: Hash + Eq, S: BuildHasher, { + #[track_caller] pub(super) fn new(map: &'a mut IndexMap, range: R, replace_with: I) -> Self where R: RangeBounds, diff --git a/src/set.rs b/src/set.rs index 5a91db95..371c653d 100644 --- a/src/set.rs +++ b/src/set.rs @@ -250,6 +250,7 @@ impl IndexSet { /// /// ***Panics*** if the starting point is greater than the end point or if /// the end point is greater than the length of the set. + #[track_caller] pub fn drain(&mut self, range: R) -> Drain<'_, T> where R: RangeBounds, @@ -264,6 +265,7 @@ impl IndexSet { /// the elements `[0, at)` with its previous capacity unchanged. /// /// ***Panics*** if `at > len`. + #[track_caller] pub fn split_off(&mut self, at: usize) -> Self where S: Clone, @@ -426,6 +428,7 @@ where /// assert_eq!(set.get_index_of(&'+'), Some(27)); /// assert_eq!(set.len(), 28); /// ``` + #[track_caller] 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()) @@ -483,6 +486,7 @@ where /// // This is an invalid index for moving an existing value! /// set.shift_insert(set.len(), 'a'); /// ``` + #[track_caller] pub fn shift_insert(&mut self, index: usize, value: T) -> bool { self.map.shift_insert(index, value, ()).is_none() } @@ -584,6 +588,7 @@ where /// assert!(set.into_iter().eq([0, 1, 5, 3, 2, 4])); /// assert_eq!(removed, &[2, 3]); /// ``` + #[track_caller] pub fn splice(&mut self, range: R, replace_with: I) -> Splice<'_, I::IntoIter, T, S> where R: RangeBounds, @@ -1050,6 +1055,7 @@ impl IndexSet { /// ***Panics*** if `from` or `to` are out of bounds. /// /// Computes in **O(n)** time (average). + #[track_caller] pub fn move_index(&mut self, from: usize, to: usize) { self.map.move_index(from, to) } @@ -1059,6 +1065,7 @@ impl IndexSet { /// ***Panics*** if `a` or `b` are out of bounds. /// /// Computes in **O(1)** time (average). + #[track_caller] pub fn swap_indices(&mut self, a: usize, b: usize) { self.map.swap_indices(a, b) } diff --git a/src/set/iter.rs b/src/set/iter.rs index 31982760..5b1c8c90 100644 --- a/src/set/iter.rs +++ b/src/set/iter.rs @@ -542,6 +542,7 @@ where T: Hash + Eq, S: BuildHasher, { + #[track_caller] pub(super) fn new(set: &'a mut IndexSet, range: R, replace_with: I) -> Self where R: RangeBounds, diff --git a/src/util.rs b/src/util.rs index 377ff516..9e7a5766 100644 --- a/src/util.rs +++ b/src/util.rs @@ -4,6 +4,7 @@ pub(crate) fn third(t: (A, B, C)) -> C { t.2 } +#[track_caller] pub(crate) fn simplify_range(range: R, len: usize) -> Range where R: RangeBounds, From 2f5575574ca31240ed539b7fdb9938207fe782cc Mon Sep 17 00:00:00 2001 From: savannstm Date: Mon, 13 Jan 2025 23:54:49 +0500 Subject: [PATCH 232/236] Improve panic messages --- src/map.rs | 37 ++++++++++++++++++++++++++++++------- src/map/core.rs | 7 ++++++- src/set.rs | 8 ++++++-- src/util.rs | 10 +++++++--- 4 files changed, 49 insertions(+), 13 deletions(-) diff --git a/src/map.rs b/src/map.rs index e19266a0..347649f8 100644 --- a/src/map.rs +++ b/src/map.rs @@ -497,7 +497,13 @@ where /// ``` #[track_caller] pub fn insert_before(&mut self, mut index: usize, key: K, value: V) -> (usize, Option) { - assert!(index <= self.len(), "index out of bounds"); + let len = self.len(); + + assert!( + index <= len, + "index out of bounds: the len is {len} but the index is {index}. Expected index <= len" + ); + match self.entry(key) { Entry::Occupied(mut entry) => { if index > entry.index() { @@ -579,13 +585,21 @@ where let len = self.len(); match self.entry(key) { Entry::Occupied(mut entry) => { - assert!(index < len, "index out of bounds"); + assert!( + index < len, + "index out of bounds: the len is {len} but the index is {index}" + ); + let old = mem::replace(entry.get_mut(), value); entry.move_index(index); Some(old) } Entry::Vacant(entry) => { - assert!(index <= len, "index out of bounds"); + assert!( + index <= len, + "index out of bounds: the len is {len} but the index is {index}. Expected index <= len" + ); + entry.shift_insert(index, value); None } @@ -1332,7 +1346,7 @@ where /// /// ***Panics*** if `key` is not present in the map. fn index(&self, key: &Q) -> &V { - self.get(key).expect("IndexMap: key not found") + self.get(key).expect("no entry found for key") } } @@ -1374,7 +1388,7 @@ where /// /// ***Panics*** if `key` is not present in the map. fn index_mut(&mut self, key: &Q) -> &mut V { - self.get_mut(key).expect("IndexMap: key not found") + self.get_mut(key).expect("no entry found for key") } } @@ -1418,7 +1432,12 @@ impl Index for IndexMap { /// ***Panics*** if `index` is out of bounds. fn index(&self, index: usize) -> &V { self.get_index(index) - .expect("IndexMap: index out of bounds") + .unwrap_or_else(|| { + panic!( + "index out of bounds: the len is {len} but the index is {index}", + len = self.len() + ); + }) .1 } } @@ -1457,8 +1476,12 @@ impl IndexMut for IndexMap { /// /// ***Panics*** if `index` is out of bounds. fn index_mut(&mut self, index: usize) -> &mut V { + let len: usize = self.len(); + self.get_index_mut(index) - .expect("IndexMap: index out of bounds") + .unwrap_or_else(|| { + panic!("index out of bounds: the len is {len} but the index is {index}"); + }) .1 } } diff --git a/src/map/core.rs b/src/map/core.rs index 20f68343..7a000d90 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -208,7 +208,12 @@ impl IndexMapCore { #[track_caller] pub(crate) fn split_off(&mut self, at: usize) -> Self { - assert!(at <= self.entries.len()); + let len = self.entries.len(); + assert!( + at <= len, + "index out of bounds: the len is {len} but the index is {at}. Expected index <= len" + ); + self.erase_indices(at, self.entries.len()); let entries = self.entries.split_off(at); diff --git a/src/set.rs b/src/set.rs index 371c653d..1be248eb 100644 --- a/src/set.rs +++ b/src/set.rs @@ -1106,8 +1106,12 @@ impl Index for IndexSet { /// /// ***Panics*** if `index` is out of bounds. fn index(&self, index: usize) -> &T { - self.get_index(index) - .expect("IndexSet: index out of bounds") + self.get_index(index).unwrap_or_else(|| { + panic!( + "index out of bounds: the len is {len} but the index is {index}", + len = self.len() + ); + }) } } diff --git a/src/util.rs b/src/util.rs index 9e7a5766..6fb6fc3c 100644 --- a/src/util.rs +++ b/src/util.rs @@ -13,17 +13,21 @@ where Bound::Unbounded => 0, Bound::Included(&i) if i <= len => i, Bound::Excluded(&i) if i < len => i + 1, - bound => panic!("range start {:?} should be <= length {}", bound, len), + Bound::Included(i) | Bound::Excluded(i) => { + panic!("range start index {i} out of range for slice of length {len}") + } }; let end = match range.end_bound() { Bound::Unbounded => len, Bound::Excluded(&i) if i <= len => i, Bound::Included(&i) if i < len => i + 1, - bound => panic!("range end {:?} should be <= length {}", bound, len), + Bound::Included(i) | Bound::Excluded(i) => { + panic!("range end index {i} out of range for slice of length {len}") + } }; if start > end { panic!( - "range start {:?} should be <= range end {:?}", + "range start index {:?} should be <= range end index {:?}", range.start_bound(), range.end_bound() ); From 1f1272171f108c23865d1943b9605b54a19de185 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 14 Jan 2025 16:41:57 -0800 Subject: [PATCH 233/236] Improve memory reservation for `insert_entry` In `core::RefMut::insert_unique`, used by `insert_entry` and others, we were calling `reserve_entries` *before* the table insert, which defeats the goal of matching capacities. We can't directly call that after table insert though, because we'll be holding an `OccupiedEntry` that prevents looking at the table itself. Instead, this code path now uses a more typical doubling growth on the entries `Vec` itself, but still enhanced by considering `MAX_ENTRIES_CAPACITY` as well. --- src/map/core.rs | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/map/core.rs b/src/map/core.rs index 7cebb466..66d5e1f5 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -512,6 +512,18 @@ impl IndexMapCore { } } +/// Reserve entries capacity, rounded up to match the indices (via `try_capacity`). +fn reserve_entries(entries: &mut Entries, additional: usize, try_capacity: usize) { + // Use a soft-limit on the maximum capacity, but if the caller explicitly + // requested more, do it and let them have the resulting panic. + let try_capacity = try_capacity.min(IndexMapCore::::MAX_ENTRIES_CAPACITY); + let try_add = try_capacity - entries.len(); + if try_add > additional && entries.try_reserve_exact(try_add).is_ok() { + return; + } + entries.reserve_exact(additional); +} + impl<'a, K, V> RefMut<'a, K, V> { #[inline] fn new(indices: &'a mut Indices, entries: &'a mut Entries) -> Self { @@ -519,33 +531,25 @@ impl<'a, K, V> RefMut<'a, K, V> { } /// Reserve entries capacity, rounded up to match the indices + #[inline] fn reserve_entries(&mut self, additional: usize) { - // Use a soft-limit on the maximum capacity, but if the caller explicitly - // requested more, do it and let them have the resulting panic. - let new_capacity = Ord::min( - self.indices.capacity(), - IndexMapCore::::MAX_ENTRIES_CAPACITY, - ); - let try_add = new_capacity - self.entries.len(); - if try_add > additional && self.entries.try_reserve_exact(try_add).is_ok() { - return; - } - self.entries.reserve_exact(additional); + reserve_entries(self.entries, additional, self.indices.capacity()); } /// Insert a key-value pair in `entries`, /// *without* checking whether it already exists. - fn insert_unique(mut self, hash: HashValue, key: K, value: V) -> OccupiedEntry<'a, K, V> { - if self.entries.len() == self.entries.capacity() { - // Reserve our own capacity synced to the indices, - // rather than letting `Vec::push` just double it. - self.reserve_entries(1); - } + fn insert_unique(self, hash: HashValue, key: K, value: V) -> OccupiedEntry<'a, K, V> { let i = self.indices.len(); + debug_assert_eq!(i, self.entries.len()); let entry = self .indices .insert_unique(hash.get(), i, get_hash(self.entries)); - debug_assert_eq!(i, self.entries.len()); + if self.entries.len() == self.entries.capacity() { + // We can't call `indices.capacity()` while this `entry` has borrowed it, so we'll have + // to amortize growth on our own. It's still an improvement over the basic `Vec::push` + // doubling though, since we also consider `MAX_ENTRIES_CAPACITY`. + reserve_entries(self.entries, 1, 2 * self.entries.capacity()); + } self.entries.push(Bucket { hash, key, value }); OccupiedEntry::new(self.entries, entry) } From f61b581178260fe2afaf1c0fc0613be319827d19 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sun, 19 Jan 2025 16:45:43 -0800 Subject: [PATCH 234/236] Release 2.7.1 --- Cargo.toml | 2 +- RELEASES.md | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1ac50605..9e00e1d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "indexmap" edition = "2021" -version = "2.7.0" +version = "2.7.1" 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 befdd0e0..8867a49c 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,10 @@ # Releases +## 2.7.1 (2025-01-19) + +- Added `#[track_caller]` to functions that may panic. +- Improved memory reservation for `insert_entry`. + ## 2.7.0 (2024-11-30) - Added methods `Entry::insert_entry` and `VacantEntry::insert_entry`, returning From b135634b457d7590abeb662a81d8e5f8baa70806 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 21 Jan 2025 10:50:34 -0800 Subject: [PATCH 235/236] Upgrade dev-dependency `itertools` to 0.14 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9e00e1d7..6c2bad5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ version = "0.15.0" default-features = false [dev-dependencies] -itertools = "0.13" +itertools = "0.14" rand = {version = "0.8", features = ["small_rng"] } quickcheck = { version = "1.0", default-features = false } fnv = "1.0" From d057291e3218c31a570bc30f3138dd64a7ba73de Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 21 Jan 2025 15:51:10 -0800 Subject: [PATCH 236/236] ci: use cargo-nextest with miri --- .github/workflows/ci.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index efcb2066..71ad595e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -113,8 +113,12 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly with: - components: miri - - run: cargo miri test + components: miri, rust-src + - uses: taiki-e/install-action@v2 + with: + tool: cargo-nextest + - run: cargo miri nextest run + - run: cargo miri test --doc minimal-versions: name: Check MSRV and minimal-versions