diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87ca19b..e166236 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,8 @@ jobs: features: rayon - rust: stable features: serde + - rust: stable + features: sval - rust: stable features: borsh - rust: stable @@ -62,6 +64,10 @@ jobs: if: matrix.features == 'serde' run: | cargo test --verbose -p test-serde + - name: Tests (sval) + if: matrix.features == 'sval' + run: | + cargo test --verbose -p test-sval - name: Test run benchmarks if: matrix.bench != '' run: cargo test -v --benches @@ -141,7 +147,7 @@ jobs: - name: Build (nightly) run: cargo +nightly build --verbose --all-features - name: Build (MSRV) - run: cargo build --verbose --features arbitrary,quickcheck,serde,rayon + run: cargo build --verbose --features arbitrary,quickcheck,serde,sval,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. diff --git a/Cargo.toml b/Cargo.toml index 82d846e..3f4951d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "ordermap" edition = "2021" -version = "0.5.8" +version = "0.5.9" documentation = "https://docs.rs/ordermap/" repository = "https://github.com/indexmap-rs/ordermap" license = "Apache-2.0 OR MIT" @@ -14,13 +14,14 @@ rust-version = "1.63" bench = false [dependencies] -indexmap = { version = "2.10.0", default-features = false } +indexmap = { version = "2.11.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.5.6", optional = true, default-features = false } rayon = { version = "1.9", optional = true } +sval = { version = "2", optional = true, default-features = false } [dev-dependencies] itertools = "0.14" @@ -37,6 +38,7 @@ arbitrary = ["dep:arbitrary", "indexmap/arbitrary"] quickcheck = ["dep:quickcheck", "indexmap/quickcheck"] rayon = ["dep:rayon", "indexmap/rayon"] serde = ["dep:serde", "indexmap/serde"] +sval = ["dep:sval", "indexmap/sval"] borsh = ["dep:borsh", "borsh/indexmap"] [profile.bench] @@ -48,11 +50,11 @@ sign-tag = true tag-name = "{{version}}" [package.metadata.docs.rs] -features = ["arbitrary", "quickcheck", "serde", "borsh", "rayon"] +features = ["arbitrary", "quickcheck", "serde", "borsh", "rayon", "sval"] rustdoc-args = ["--cfg", "docsrs"] [workspace] -members = ["test-nostd", "test-serde"] +members = ["test-nostd", "test-serde", "test-sval"] [lints.clippy] style = "allow" diff --git a/RELEASES.md b/RELEASES.md index 405baa9..6732bab 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,17 @@ # Releases +## 0.5.9 (2025-08-22) + +- Added `insert_sorted_by` and `insert_sorted_by_key` methods to `OrderMap`, + `OrderSet`, and `VacantEntry`, like customizable versions of `insert_sorted`. +- Added `is_sorted`, `is_sorted_by`, and `is_sorted_by_key` methods to + `OrderMap` and `OrderSet`, as well as their `Slice` counterparts. +- Added `sort_by_key` and `sort_unstable_by_key` methods to `OrderMap` and + `OrderSet`, as well as parallel counterparts. +- Added `replace_index` methods to `OrderMap`, `OrderSet`, and `VacantEntry` + to replace the key (or set value) at a given index. +- Added optional `sval` serialization support. + ## 0.5.8 (2025-06-26) - Added `extract_if` methods to `OrderMap` and `OrderSet`, similar to the diff --git a/src/lib.rs b/src/lib.rs index e50038c..658e75a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,6 +110,8 @@ mod macros; mod borsh; #[cfg(feature = "serde")] mod serde; +#[cfg(feature = "sval")] +mod sval; pub mod map; pub mod set; diff --git a/src/map.rs b/src/map.rs index ae64ca9..8a057b6 100644 --- a/src/map.rs +++ b/src/map.rs @@ -466,6 +466,51 @@ where self.inner.insert_sorted(key, value) } + /// Insert a key-value pair in the map at its ordered position among keys + /// sorted by `cmp`. + /// + /// This is equivalent to finding the position with + /// [`binary_search_by`][Self::binary_search_by], then calling + /// [`insert_before`][Self::insert_before] with the given key and 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 moved to or inserted at that position regardless. + /// + /// Computes in **O(n)** time (average). + pub fn insert_sorted_by(&mut self, key: K, value: V, cmp: F) -> (usize, Option) + where + K: Ord, + F: FnMut(&K, &V, &K, &V) -> Ordering, + { + self.inner.insert_sorted_by(key, value, cmp) + } + + /// Insert a key-value pair in the map at its ordered position + /// using a sort-key extraction function. + /// + /// This is equivalent to finding the position with + /// [`binary_search_by_key`][Self::binary_search_by_key] with `sort_key(key)`, then + /// calling [`insert_before`][Self::insert_before] with the given key and 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 moved to or inserted at that position regardless. + /// + /// Computes in **O(n)** time (average). + pub fn insert_sorted_by_key( + &mut self, + key: K, + value: V, + sort_key: F, + ) -> (usize, Option) + where + B: Ord, + F: FnMut(&K, &V) -> B, + { + self.inner.insert_sorted_by_key(key, value, sort_key) + } + /// 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 @@ -581,7 +626,27 @@ where self.inner.shift_insert(index, key, value) } - /// Get the given key’s corresponding entry in the map for insertion and/or + /// Replaces the key at the given index. The new key does not need to be + /// equivalent to the one it is replacing, but it must be unique to the rest + /// of the map. + /// + /// Returns `Ok(old_key)` if successful, or `Err((other_index, key))` if an + /// equivalent key already exists at a different index. The map will be + /// unchanged in the error case. + /// + /// Direct indexing can be used to change the corresponding value: simply + /// `map[index] = value`, or `mem::replace(&mut map[index], value)` to + /// retrieve the old value as well. + /// + /// ***Panics*** if `index` is out of bounds. + /// + /// Computes in **O(1)** time (average). + #[track_caller] + pub fn replace_index(&mut self, index: usize, key: K) -> Result { + self.inner.replace_index(index, key) + } + + /// Get the given key's corresponding entry in the map for insertion and/or /// in-place manipulation. /// /// Computes in **O(1)** time (amortized average). @@ -869,7 +934,7 @@ impl OrderMap { self.inner.retain(keep); } - /// Sort the map’s key-value pairs by the default ordering of the keys. + /// 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 @@ -883,7 +948,7 @@ impl OrderMap { self.inner.sort_keys(); } - /// Sort the map’s key-value pairs in place using the comparison + /// Sort the map's key-value pairs in place using the comparison /// function `cmp`. /// /// The comparison function receives two key and value pairs to compare (you @@ -909,6 +974,18 @@ impl OrderMap { self.inner.sorted_by(cmp) } + /// Sort the map's key-value pairs in place using a sort-key extraction function. + /// + /// Computes in **O(n log n + c)** time and **O(n)** space where *n* is + /// the length of the map and *c* the capacity. The sort is stable. + pub fn sort_by_key(&mut self, sort_key: F) + where + T: Ord, + F: FnMut(&K, &V) -> T, + { + self.inner.sort_by_key(sort_key) + } + /// Sort the map's key-value pairs by the default ordering of the keys, but /// may not preserve the order of equal elements. /// @@ -947,7 +1024,19 @@ impl OrderMap { self.inner.sorted_unstable_by(cmp) } - /// Sort the map’s key-value pairs in place using a sort-key extraction function. + /// Sort the map's key-value pairs in place using a sort-key extraction function. + /// + /// 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_key(&mut self, sort_key: F) + where + T: Ord, + F: FnMut(&K, &V) -> T, + { + self.inner.sort_unstable_by_key(sort_key) + } + + /// 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 @@ -1006,6 +1095,34 @@ impl OrderMap { self.inner.binary_search_by_key(b, f) } + /// Checks if the keys of this map are sorted. + #[inline] + pub fn is_sorted(&self) -> bool + where + K: PartialOrd, + { + self.inner.is_sorted() + } + + /// Checks if this map is sorted using the given comparator function. + #[inline] + pub fn is_sorted_by<'a, F>(&'a self, cmp: F) -> bool + where + F: FnMut(&'a K, &'a V, &'a K, &'a V) -> bool, + { + self.inner.is_sorted_by(cmp) + } + + /// Checks if this map is sorted using the given sort-key function. + #[inline] + pub fn is_sorted_by_key<'a, F, T>(&'a self, sort_key: F) -> bool + where + F: FnMut(&'a K, &'a V) -> T, + T: PartialOrd, + { + self.inner.is_sorted_by_key(sort_key) + } + /// 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). /// @@ -1020,7 +1137,7 @@ impl OrderMap { self.inner.partition_point(pred) } - /// Reverses the order of the map’s key-value pairs in place. + /// Reverses the order of the map's key-value pairs in place. /// /// Computes in **O(n)** time and **O(1)** space. pub fn reverse(&mut self) { diff --git a/src/map/entry.rs b/src/map/entry.rs index 7bf1533..759083e 100644 --- a/src/map/entry.rs +++ b/src/map/entry.rs @@ -1,3 +1,4 @@ +use core::cmp::Ordering; use core::fmt; use indexmap::map as ix; @@ -326,6 +327,40 @@ impl<'a, K, V> VacantEntry<'a, K, V> { self.inner.insert_sorted(value) } + /// Inserts the entry's key and the given value into the map at its ordered + /// position among keys sorted by `cmp`, 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_by(self, value: V, cmp: F) -> (usize, &'a mut V) + where + K: Ord, + F: FnMut(&K, &V, &K, &V) -> Ordering, + { + self.inner.insert_sorted_by(value, cmp) + } + + /// Inserts the entry's key and the given value into the map at its ordered + /// position using a sort-key extraction function, 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_by_key(self, value: V, sort_key: F) -> (usize, &'a mut V) + where + B: Ord, + F: FnMut(&K, &V) -> B, + { + self.inner.insert_sorted_by_key(value, sort_key) + } + /// 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. /// @@ -336,6 +371,18 @@ impl<'a, K, V> VacantEntry<'a, K, V> { pub fn shift_insert(self, index: usize, value: V) -> &'a mut V { self.inner.shift_insert(index, value) } + + /// Replaces the key at the given index with this entry's key, returning the + /// old key and an `OccupiedEntry` for that index. + /// + /// ***Panics*** if `index` is out of bounds. + /// + /// Computes in **O(1)** time (average). + #[track_caller] + pub fn replace_index(self, index: usize) -> (K, OccupiedEntry<'a, K, V>) { + let (old_key, inner) = self.inner.replace_index(index); + (old_key, OccupiedEntry { inner }) + } } impl fmt::Debug for VacantEntry<'_, K, V> { diff --git a/src/map/mutable.rs b/src/map/mutable.rs index 5f59dcf..c81bdf3 100644 --- a/src/map/mutable.rs +++ b/src/map/mutable.rs @@ -8,7 +8,7 @@ use indexmap::map::MutableKeys as _; /// 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**. +/// 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 @@ -88,7 +88,7 @@ where /// 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**. +/// 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 diff --git a/src/map/rayon.rs b/src/map/rayon.rs index 7381320..a0d0c75 100644 --- a/src/map/rayon.rs +++ b/src/map/rayon.rs @@ -126,7 +126,7 @@ where K: Send, V: Send, { - /// Sort the map’s key-value pairs in parallel, by the default ordering of the keys. + /// Sort the map's key-value pairs in parallel, by the default ordering of the keys. pub fn par_sort_keys(&mut self) where K: Ord, @@ -134,7 +134,7 @@ where self.inner.par_sort_keys(); } - /// Sort the map’s key-value pairs in place and in parallel, using the comparison + /// 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 @@ -155,6 +155,16 @@ where self.inner.par_sorted_by(cmp) } + /// Sort the map's key-value pairs in place and in parallel, using a sort-key extraction + /// function. + pub fn par_sort_by_key(&mut self, sort_key: F) + where + T: Ord, + F: Fn(&K, &V) -> T + Sync, + { + self.inner.par_sort_by_key(sort_key) + } + /// 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 @@ -184,7 +194,17 @@ where self.inner.par_sorted_unstable_by(cmp) } - /// Sort the map’s key-value pairs in place and in parallel, using a sort-key extraction + /// Sort the map's key-value pairs in place and in parallel, using a sort-key extraction + /// function. + pub fn par_sort_unstable_by_key(&mut self, sort_key: F) + where + T: Ord, + F: Fn(&K, &V) -> T + Sync, + { + self.inner.par_sort_unstable_by_key(sort_key) + } + + /// 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 diff --git a/src/map/tests.rs b/src/map/tests.rs index a1899c8..0341155 100644 --- a/src/map/tests.rs +++ b/src/map/tests.rs @@ -1208,3 +1208,87 @@ fn disjoint_indices_mut_fail_duplicate() { Err(crate::GetDisjointMutError::OverlappingIndices) ); } + +#[test] +fn insert_sorted_by_key() { + let mut values = [(-1, 8), (3, 18), (-27, 2), (-2, 5)]; + let mut map: OrderMap = OrderMap::new(); + for (key, value) in values { + let (_, old) = map.insert_sorted_by_key(key, value, |k, _| k.abs()); + assert_eq!(old, None); + } + values.sort_by_key(|(key, _)| key.abs()); + assert_eq!(values, *map.as_slice()); + + for (key, value) in &mut values { + let (_, old) = map.insert_sorted_by_key(*key, -*value, |k, _| k.abs()); + assert_eq!(old, Some(*value)); + *value = -*value; + } + assert_eq!(values, *map.as_slice()); +} + +#[test] +fn insert_sorted_by() { + let mut values = [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]; + let mut map: OrderMap = OrderMap::new(); + for (key, value) in values { + let (_, old) = map.insert_sorted_by(key, value, |key1, _, key2, _| key2.cmp(key1)); + assert_eq!(old, None); + } + values.reverse(); + assert_eq!(values, *map.as_slice()); + + for (key, value) in &mut values { + let (_, old) = map.insert_sorted_by(*key, -*value, |key1, _, key2, _| key2.cmp(key1)); + assert_eq!(old, Some(*value)); + *value = -*value; + } + assert_eq!(values, *map.as_slice()); +} + +#[test] +fn is_sorted() { + fn expect(map: &OrderMap, e: [bool; 7]) { + assert_eq!(e[0], map.is_sorted()); + assert_eq!(e[1], map.is_sorted_by(|k1, _, k2, _| k1 < k2)); + assert_eq!(e[2], map.is_sorted_by(|k1, _, k2, _| k1 > k2)); + assert_eq!(e[3], map.is_sorted_by(|_, v1, _, v2| v1 < v2)); + assert_eq!(e[4], map.is_sorted_by(|_, v1, _, v2| v1 > v2)); + assert_eq!(e[5], map.is_sorted_by_key(|k, _| k)); + assert_eq!(e[6], map.is_sorted_by_key(|_, v| v)); + } + + let mut map = OrderMap::from_iter((0..10).map(|i| (i, i * i))); + expect(&map, [true, true, false, true, false, true, true]); + + map[5] = -1; + expect(&map, [true, true, false, false, false, true, false]); + + map[5] = 25; + map.replace_index(5, -1).unwrap(); + expect(&map, [false, false, false, true, false, false, true]); +} + +#[test] +fn is_sorted_trivial() { + fn expect(map: &OrderMap, e: [bool; 5]) { + assert_eq!(e[0], map.is_sorted()); + assert_eq!(e[1], map.is_sorted_by(|_, _, _, _| true)); + assert_eq!(e[2], map.is_sorted_by(|_, _, _, _| false)); + assert_eq!(e[3], map.is_sorted_by_key(|_, _| 0f64)); + assert_eq!(e[4], map.is_sorted_by_key(|_, _| f64::NAN)); + } + + let mut map = OrderMap::new(); + expect(&map, [true, true, true, true, true]); + + map.insert(0, 0); + expect(&map, [true, true, true, true, true]); + + map.insert(1, 1); + expect(&map, [true, true, false, true, false]); + + map.reverse(); + expect(&map, [false, true, false, true, false]); +} diff --git a/src/set.rs b/src/set.rs index e6c7747..705e384 100644 --- a/src/set.rs +++ b/src/set.rs @@ -405,6 +405,46 @@ where self.inner.insert_sorted(value) } + /// Insert the value into the set at its ordered position among values + /// sorted by `cmp`. + /// + /// This is equivalent to finding the position with + /// [`binary_search_by`][Self::binary_search_by], then calling + /// [`insert_before`][Self::insert_before]. + /// + /// 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). + pub fn insert_sorted_by(&mut self, value: T, cmp: F) -> (usize, bool) + where + T: Ord, + F: FnMut(&T, &T) -> Ordering, + { + self.inner.insert_sorted_by(value, cmp) + } + + /// Insert the value into the set at its ordered position among values + /// using a sort-key extraction function. + /// + /// This is equivalent to finding the position with + /// [`binary_search_by_key`][Self::binary_search_by_key] with `sort_key(key)`, + /// then calling [`insert_before`][Self::insert_before]. + /// + /// 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). + pub fn insert_sorted_by_key(&mut self, value: T, sort_key: F) -> (usize, bool) + where + B: Ord, + F: FnMut(&T) -> B, + { + self.inner.insert_sorted_by_key(value, sort_key) + } + /// 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 @@ -528,6 +568,22 @@ where self.inner.replace_full(value) } + /// Replaces the value at the given index. The new value does not need to be + /// equivalent to the one it is replacing, but it must be unique to the rest + /// of the set. + /// + /// Returns `Ok(old_value)` if successful, or `Err((other_index, value))` if + /// an equivalent value already exists at a different index. The set will be + /// unchanged in the error case. + /// + /// ***Panics*** if `index` is out of bounds. + /// + /// Computes in **O(1)** time (average). + #[track_caller] + pub fn replace_index(&mut self, index: usize, value: T) -> Result { + self.inner.replace_index(index, value) + } + /// 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`. @@ -809,7 +865,7 @@ impl OrderSet { self.inner.retain(keep) } - /// Sort the set’s values by their default ordering. + /// 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 @@ -823,7 +879,7 @@ impl OrderSet { self.inner.sort() } - /// Sort the set’s values in place using the comparison function `cmp`. + /// 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, cmp: F) @@ -844,6 +900,17 @@ impl OrderSet { self.inner.sorted_by(cmp) } + /// Sort the set's values in place using a key extraction function. + /// + /// Computes in **O(n log n)** time and **O(n)** space. The sort is stable. + pub fn sort_by_key(&mut self, sort_key: F) + where + K: Ord, + F: FnMut(&T) -> K, + { + self.inner.sort_by_key(sort_key) + } + /// Sort the set's values by their default ordering. /// /// See [`sort_unstable_by`](Self::sort_unstable_by) for details. @@ -873,7 +940,18 @@ impl OrderSet { self.inner.sorted_unstable_by(cmp) } - /// Sort the set’s values in place using a key extraction function. + /// Sort the set's values in place using a key extraction function. + /// + /// Computes in **O(n log n)** time. The sort is unstable. + pub fn sort_unstable_by_key(&mut self, sort_key: F) + where + K: Ord, + F: FnMut(&T) -> K, + { + self.inner.sort_unstable_by_key(sort_key) + } + + /// 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 @@ -932,6 +1010,34 @@ impl OrderSet { self.inner.binary_search_by_key(b, f) } + /// Checks if the values of this set are sorted. + #[inline] + pub fn is_sorted(&self) -> bool + where + T: PartialOrd, + { + self.inner.is_sorted() + } + + /// Checks if this set is sorted using the given comparator function. + #[inline] + pub fn is_sorted_by<'a, F>(&'a self, cmp: F) -> bool + where + F: FnMut(&'a T, &'a T) -> bool, + { + self.inner.is_sorted_by(cmp) + } + + /// Checks if this set is sorted using the given sort-key function. + #[inline] + pub fn is_sorted_by_key<'a, F, K>(&'a self, sort_key: F) -> bool + where + F: FnMut(&'a T) -> K, + K: PartialOrd, + { + self.inner.is_sorted_by_key(sort_key) + } + /// 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). /// @@ -946,7 +1052,7 @@ impl OrderSet { self.inner.partition_point(pred) } - /// Reverses the order of the set’s values in place. + /// Reverses the order of the set's values in place. /// /// Computes in **O(n)** time and **O(1)** space. pub fn reverse(&mut self) { diff --git a/src/set/mutable.rs b/src/set/mutable.rs index 85bff26..3256924 100644 --- a/src/set/mutable.rs +++ b/src/set/mutable.rs @@ -7,7 +7,7 @@ use indexmap::set::MutableValues as _; /// 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**. +/// 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 diff --git a/src/set/rayon.rs b/src/set/rayon.rs index 1e9e2db..e565214 100644 --- a/src/set/rayon.rs +++ b/src/set/rayon.rs @@ -176,7 +176,7 @@ impl OrderSet where T: Send, { - /// Sort the set’s values in parallel by their default ordering. + /// Sort the set's values in parallel by their default ordering. pub fn par_sort(&mut self) where T: Ord, @@ -184,7 +184,7 @@ where self.inner.par_sort(); } - /// Sort the set’s values in place and in parallel, using the comparison function `cmp`. + /// 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, @@ -201,6 +201,15 @@ where self.inner.par_sorted_by(cmp) } + /// Sort the set's values in place and in parallel, using a key extraction function. + pub fn par_sort_by_key(&mut self, sort_key: F) + where + K: Ord, + F: Fn(&T) -> K + Sync, + { + self.inner.par_sort_by_key(sort_key) + } + /// Sort the set's values in parallel by their default ordering. pub fn par_sort_unstable(&mut self) where @@ -209,7 +218,7 @@ where self.inner.par_sort_unstable(); } - /// Sort the set’s values in place and in parallel, using the comparison function `cmp`. + /// 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, @@ -226,7 +235,16 @@ where self.inner.par_sorted_unstable_by(cmp) } - /// Sort the set’s values in place and in parallel, using a key extraction function. + /// Sort the set's values in place and in parallel, using a key extraction function. + pub fn par_sort_unstable_by_key(&mut self, sort_key: F) + where + K: Ord, + F: Fn(&T) -> K + Sync, + { + self.inner.par_sort_unstable_by_key(sort_key) + } + + /// 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, diff --git a/src/set/tests.rs b/src/set/tests.rs index 855058f..db4597d 100644 --- a/src/set/tests.rs +++ b/src/set/tests.rs @@ -768,6 +768,28 @@ fn first() { assert!(result.is_none()); } +#[test] +fn sort_by_key() { + let mut index_set: OrderSet = OrderSet::new(); + index_set.insert(3); + index_set.insert(1); + index_set.insert(2); + index_set.insert(0); + index_set.sort_by_key(|&x| -x); + assert_eq!(index_set.as_slice(), &[3, 2, 1, 0]); +} + +#[test] +fn sort_unstable_by_key() { + let mut index_set: OrderSet = OrderSet::new(); + index_set.insert(3); + index_set.insert(1); + index_set.insert(2); + index_set.insert(0); + index_set.sort_unstable_by_key(|&x| -x); + assert_eq!(index_set.as_slice(), &[3, 2, 1, 0]); +} + #[test] fn sort_by_cached_key() { let mut index_set: OrderSet = OrderSet::new(); @@ -1004,3 +1026,42 @@ fn test_partition_point() { assert_eq!(b.partition_point(|&x| x < 7), 2); assert_eq!(b.partition_point(|&x| x < 8), 3); } + +#[test] +fn is_sorted() { + fn expect(set: &OrderSet, e: [bool; 4]) { + assert_eq!(e[0], set.is_sorted()); + assert_eq!(e[1], set.is_sorted_by(|v1, v2| v1 < v2)); + assert_eq!(e[2], set.is_sorted_by(|v1, v2| v1 > v2)); + assert_eq!(e[3], set.is_sorted_by_key(|v| v)); + } + + let mut set = OrderSet::::from_iter(0..10); + expect(&set, [true, true, false, true]); + + set.replace_index(5, -1).unwrap(); + expect(&set, [false, false, false, false]); +} + +#[test] +fn is_sorted_trivial() { + fn expect(set: &OrderSet, e: [bool; 5]) { + assert_eq!(e[0], set.is_sorted()); + assert_eq!(e[1], set.is_sorted_by(|_, _| true)); + assert_eq!(e[2], set.is_sorted_by(|_, _| false)); + assert_eq!(e[3], set.is_sorted_by_key(|_| 0f64)); + assert_eq!(e[4], set.is_sorted_by_key(|_| f64::NAN)); + } + + let mut set = OrderSet::::default(); + expect(&set, [true, true, true, true, true]); + + set.insert(0); + expect(&set, [true, true, true, true, true]); + + set.insert(1); + expect(&set, [true, true, false, true, false]); + + set.reverse(); + expect(&set, [false, true, false, true, false]); +} diff --git a/src/sval.rs b/src/sval.rs new file mode 100644 index 0000000..0f406f3 --- /dev/null +++ b/src/sval.rs @@ -0,0 +1,16 @@ +#![cfg_attr(docsrs, doc(cfg(feature = "sval")))] + +use crate::{OrderMap, OrderSet}; +use sval::{Stream, Value}; + +impl Value for OrderMap { + fn stream<'sval, ST: Stream<'sval> + ?Sized>(&'sval self, stream: &mut ST) -> sval::Result { + self.inner.stream(stream) + } +} + +impl Value for OrderSet { + fn stream<'sval, ST: Stream<'sval> + ?Sized>(&'sval self, stream: &mut ST) -> sval::Result { + self.inner.stream(stream) + } +} diff --git a/test-sval/Cargo.toml b/test-sval/Cargo.toml new file mode 100644 index 0000000..f581704 --- /dev/null +++ b/test-sval/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "test-sval" +version = "0.1.0" +publish = false +edition = "2021" + +[dependencies] + +[dev-dependencies] +fnv = "1.0" +ordermap = { path = "..", features = ["sval"] } +sval = { version = "2", features = ["derive"] } +sval_test = "2" diff --git a/test-sval/src/lib.rs b/test-sval/src/lib.rs new file mode 100644 index 0000000..164cd8d --- /dev/null +++ b/test-sval/src/lib.rs @@ -0,0 +1,104 @@ +#![cfg(test)] + +use fnv::FnvBuildHasher; +use ordermap::{ordermap, orderset, OrderMap, OrderSet}; +use sval_test::{assert_tokens, Token}; + +#[test] +fn test_sval_map() { + let map = ordermap! { 1 => 2, 3 => 4 }; + assert_tokens( + &map, + &[ + Token::MapBegin(Some(2)), + Token::MapKeyBegin, + Token::I32(1), + Token::MapKeyEnd, + Token::MapValueBegin, + Token::I32(2), + Token::MapValueEnd, + Token::MapKeyBegin, + Token::I32(3), + Token::MapKeyEnd, + Token::MapValueBegin, + Token::I32(4), + Token::MapValueEnd, + Token::MapEnd, + ], + ); +} + +#[test] +fn test_sval_set() { + let set = orderset! { 1, 2, 3, 4 }; + assert_tokens( + &set, + &[ + Token::SeqBegin(Some(4)), + Token::SeqValueBegin, + Token::I32(1), + Token::SeqValueEnd, + Token::SeqValueBegin, + Token::I32(2), + Token::SeqValueEnd, + Token::SeqValueBegin, + Token::I32(3), + Token::SeqValueEnd, + Token::SeqValueBegin, + Token::I32(4), + Token::SeqValueEnd, + Token::SeqEnd, + ], + ); +} + +#[test] +fn test_sval_map_fnv_hasher() { + let mut map: OrderMap = Default::default(); + map.insert(1, 2); + map.insert(3, 4); + assert_tokens( + &map, + &[ + Token::MapBegin(Some(2)), + Token::MapKeyBegin, + Token::I32(1), + Token::MapKeyEnd, + Token::MapValueBegin, + Token::I32(2), + Token::MapValueEnd, + Token::MapKeyBegin, + Token::I32(3), + Token::MapKeyEnd, + Token::MapValueBegin, + Token::I32(4), + Token::MapValueEnd, + Token::MapEnd, + ], + ); +} + +#[test] +fn test_sval_set_fnv_hasher() { + let mut set: OrderSet = Default::default(); + set.extend(1..5); + assert_tokens( + &set, + &[ + Token::SeqBegin(Some(4)), + Token::SeqValueBegin, + Token::I32(1), + Token::SeqValueEnd, + Token::SeqValueBegin, + Token::I32(2), + Token::SeqValueEnd, + Token::SeqValueBegin, + Token::I32(3), + Token::SeqValueEnd, + Token::SeqValueBegin, + Token::I32(4), + Token::SeqValueEnd, + Token::SeqEnd, + ], + ); +} diff --git a/tests/quick.rs b/tests/quick.rs index 3c86255..75bfa25 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -130,6 +130,100 @@ quickcheck_limit! { true } + fn insert_sorted_by(insert: Vec<(u32, u32)>) -> bool { + let mut hmap = HashMap::new(); + let mut map = OrderMap::new(); + let mut map2 = OrderMap::new(); + for &(key, value) in &insert { + hmap.insert(key, value); + map.insert_sorted_by(key, value, |key1, _, key2, _| key2.cmp(key1)); + match map2.entry(key) { + Entry::Occupied(e) => *e.into_mut() = value, + Entry::Vacant(e) => { + e.insert_sorted_by(value, |key1, _, key2, _| key2.cmp(key1)); + } + } + } + let hsorted = hmap.iter().sorted_by(|(key1, _), (key2, _)| key2.cmp(key1)); + itertools::assert_equal(hsorted, &map); + itertools::assert_equal(&map, &map2); + true + } + + fn insert_sorted_by_key(insert: Vec<(i32, u32)>) -> bool { + let mut hmap = HashMap::new(); + let mut map = OrderMap::new(); + let mut map2 = OrderMap::new(); + for &(key, value) in &insert { + hmap.insert(key, value); + map.insert_sorted_by_key(key, value, |&k, _| (k.unsigned_abs(), k)); + match map2.entry(key) { + Entry::Occupied(e) => *e.into_mut() = value, + Entry::Vacant(e) => { + e.insert_sorted_by_key(value, |&k, _| (k.unsigned_abs(), k)); + } + } + } + let hsorted = hmap.iter().sorted_by_key(|(&k, _)| (k.unsigned_abs(), k)); + itertools::assert_equal(hsorted, &map); + itertools::assert_equal(&map, &map2); + true + } + + fn replace_index(insert: Vec, index: u8, new_key: u8) -> TestResult { + if insert.is_empty() { + return TestResult::discard(); + } + let mut map = OrderMap::new(); + for &key in &insert { + map.insert(key, ()); + } + let mut index = usize::from(index); + if index < map.len() { + match map.replace_index(index, new_key) { + Ok(old_key) => { + assert!(old_key == new_key || !map.contains_key(&old_key)); + } + Err((i, key)) => { + assert_eq!(key, new_key); + index = i; + } + } + assert_eq!(map.get_index_of(&new_key), Some(index)); + assert_eq!(map.get_index(index), Some((&new_key, &()))); + TestResult::passed() + } else { + TestResult::must_fail(move || map.replace_index(index, new_key)) + } + } + + fn vacant_replace_index(insert: Vec, index: u8, new_key: u8) -> TestResult { + if insert.is_empty() { + return TestResult::discard(); + } + let mut map = OrderMap::new(); + for &key in &insert { + map.insert(key, ()); + } + let index = usize::from(index); + if let Some((&old_key, &())) = map.get_index(index) { + match map.entry(new_key) { + Entry::Occupied(_) => return TestResult::discard(), + Entry::Vacant(entry) => { + let (replaced_key, entry) = entry.replace_index(index); + assert_eq!(old_key, replaced_key); + assert_eq!(*entry.key(), new_key); + } + }; + assert!(!map.contains_key(&old_key)); + assert_eq!(map.get_index_of(&new_key), Some(index)); + assert_eq!(map.get_index(index), Some((&new_key, &()))); + TestResult::passed() + } else { + TestResult::must_fail(move || map.replace_index(index, new_key)) + } + } + fn pop(insert: Vec) -> bool { let mut map = OrderMap::new(); for &key in &insert {