Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "ordermap"
edition = "2021"
version = "0.5.7"
version = "0.5.8"
documentation = "https://docs.rs/ordermap/"
repository = "https://github.com/indexmap-rs/ordermap"
license = "Apache-2.0 OR MIT"
Expand All @@ -14,7 +14,7 @@ rust-version = "1.63"
bench = false

[dependencies]
indexmap = { version = "2.9.0", default-features = false }
indexmap = { version = "2.10.0", default-features = false }

arbitrary = { version = "1.0", optional = true, default-features = false }
quickcheck = { version = "1.0", optional = true, default-features = false }
Expand All @@ -24,10 +24,9 @@ rayon = { version = "1.9", optional = true }

[dev-dependencies]
itertools = "0.14"
rand = {version = "0.9", features = ["small_rng"] }
fastrand = { version = "2", default-features = false }
quickcheck = { version = "1.0", default-features = false }
fnv = "1.0"
lazy_static = "1.3"
serde_derive = "1.0"

[features]
Expand Down
6 changes: 6 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Releases

## 0.5.8 (2025-06-26)

- Added `extract_if` methods to `OrderMap` and `OrderSet`, similar to the
methods for `HashMap` and `HashSet` with ranges like `Vec::extract_if`.
- Added more `#[track_caller]` annotations to functions that may panic.

## 0.5.7 (2025-04-04)

- Added a `get_disjoint_mut` method to `OrderMap`, matching Rust 1.86's
Expand Down
103 changes: 45 additions & 58 deletions benches/bench.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
#![feature(test)]

extern crate test;
#[macro_use]
extern crate lazy_static;

use fnv::FnvHasher;
use std::hash::BuildHasherDefault;
use std::hash::Hash;
use std::sync::LazyLock;
type FnvBuilder = BuildHasherDefault<FnvHasher>;

use test::black_box;
Expand All @@ -16,14 +15,10 @@ use ordermap::OrderMap;

use std::collections::HashMap;

use rand::rngs::SmallRng;
use rand::seq::SliceRandom;
use rand::SeedableRng;

/// Use a consistently seeded Rng for benchmark stability
fn small_rng() -> SmallRng {
fn small_rng() -> fastrand::Rng {
let seed = u64::from_le_bytes(*b"ordermap");
SmallRng::seed_from_u64(seed)
fastrand::Rng::with_seed(seed)
}

#[bench]
Expand Down Expand Up @@ -280,7 +275,7 @@ where
{
let mut v = Vec::from_iter(iter);
let mut rng = small_rng();
v.shuffle(&mut rng);
rng.shuffle(&mut v);
v
}

Expand Down Expand Up @@ -357,53 +352,45 @@ const LOOKUP_MAP_SIZE: u32 = 100_000_u32;
const LOOKUP_SAMPLE_SIZE: u32 = 5000;
const SORT_MAP_SIZE: usize = 10_000;

// use lazy_static so that comparison benchmarks use the exact same inputs
lazy_static! {
static ref KEYS: Vec<u32> = shuffled_keys(0..LOOKUP_MAP_SIZE);
}
// use (lazy) statics so that comparison benchmarks use the exact same inputs

lazy_static! {
static ref HMAP_100K: HashMap<u32, u32> = {
let c = LOOKUP_MAP_SIZE;
let mut map = HashMap::with_capacity(c as usize);
let keys = &*KEYS;
for &key in keys {
map.insert(key, key);
}
map
};
}
static KEYS: LazyLock<Vec<u32>> = LazyLock::new(|| shuffled_keys(0..LOOKUP_MAP_SIZE));

lazy_static! {
static ref IMAP_100K: OrderMap<u32, u32> = {
let c = LOOKUP_MAP_SIZE;
let mut map = OrderMap::with_capacity(c as usize);
let keys = &*KEYS;
for &key in keys {
map.insert(key, key);
}
map
};
}
static HMAP_100K: LazyLock<HashMap<u32, u32>> = LazyLock::new(|| {
let c = LOOKUP_MAP_SIZE;
let mut map = HashMap::with_capacity(c as usize);
let keys = &*KEYS;
for &key in keys {
map.insert(key, key);
}
map
});

lazy_static! {
static ref IMAP_SORT_U32: OrderMap<u32, u32> = {
let mut map = OrderMap::with_capacity(SORT_MAP_SIZE);
for &key in &KEYS[..SORT_MAP_SIZE] {
map.insert(key, key);
}
map
};
}
lazy_static! {
static ref IMAP_SORT_S: OrderMap<String, String> = {
let mut map = OrderMap::with_capacity(SORT_MAP_SIZE);
for &key in &KEYS[..SORT_MAP_SIZE] {
map.insert(format!("{:^16x}", &key), String::new());
}
map
};
}
static IMAP_100K: LazyLock<OrderMap<u32, u32>> = LazyLock::new(|| {
let c = LOOKUP_MAP_SIZE;
let mut map = OrderMap::with_capacity(c as usize);
let keys = &*KEYS;
for &key in keys {
map.insert(key, key);
}
map
});

static IMAP_SORT_U32: LazyLock<OrderMap<u32, u32>> = LazyLock::new(|| {
let mut map = OrderMap::with_capacity(SORT_MAP_SIZE);
for &key in &KEYS[..SORT_MAP_SIZE] {
map.insert(key, key);
}
map
});

static IMAP_SORT_S: LazyLock<OrderMap<String, String>> = LazyLock::new(|| {
let mut map = OrderMap::with_capacity(SORT_MAP_SIZE);
for &key in &KEYS[..SORT_MAP_SIZE] {
map.insert(format!("{:^16x}", &key), String::new());
}
map
});

#[bench]
fn lookup_hashmap_100_000_multi(b: &mut Bencher) {
Expand Down Expand Up @@ -523,7 +510,7 @@ fn hashmap_merge_shuffle(b: &mut Bencher) {
b.iter(|| {
let mut merged = first_map.clone();
v.extend(second_map.iter().map(|(&k, &v)| (k, v)));
v.shuffle(&mut rng);
rng.shuffle(&mut v);
merged.extend(v.drain(..));

merged
Expand All @@ -550,7 +537,7 @@ fn ordermap_merge_shuffle(b: &mut Bencher) {
b.iter(|| {
let mut merged = first_map.clone();
v.extend(second_map.iter().map(|(&k, &v)| (k, v)));
v.shuffle(&mut rng);
rng.shuffle(&mut v);
merged.extend(v.drain(..));

merged
Expand All @@ -562,7 +549,7 @@ fn swap_remove_ordermap_100_000(b: &mut Bencher) {
let map = IMAP_100K.clone();
let mut keys = Vec::from_iter(map.keys().copied());
let mut rng = small_rng();
keys.shuffle(&mut rng);
rng.shuffle(&mut keys);

b.iter(|| {
let mut map = map.clone();
Expand All @@ -579,7 +566,7 @@ fn remove_ordermap_100_000_few(b: &mut Bencher) {
let map = IMAP_100K.clone();
let mut keys = Vec::from_iter(map.keys().copied());
let mut rng = small_rng();
keys.shuffle(&mut rng);
rng.shuffle(&mut keys);
keys.truncate(50);

b.iter(|| {
Expand All @@ -600,7 +587,7 @@ fn remove_ordermap_2_000_full(b: &mut Bencher) {
map.insert(key, key);
}
let mut rng = small_rng();
keys.shuffle(&mut rng);
rng.shuffle(&mut keys);

b.iter(|| {
let mut map = map.clone();
Expand Down
10 changes: 3 additions & 7 deletions benches/faststring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,15 @@ use ordermap::OrderMap;

use std::collections::HashMap;

use rand::rngs::SmallRng;
use rand::seq::SliceRandom;
use rand::SeedableRng;

use std::hash::{Hash, Hasher};

use std::borrow::Borrow;
use std::ops::Deref;

/// Use a consistently seeded Rng for benchmark stability
fn small_rng() -> SmallRng {
fn small_rng() -> fastrand::Rng {
let seed = u64::from_le_bytes(*b"ordermap");
SmallRng::seed_from_u64(seed)
fastrand::Rng::with_seed(seed)
}

#[derive(PartialEq, Eq, Copy, Clone)]
Expand Down Expand Up @@ -68,7 +64,7 @@ where
{
let mut v = Vec::from_iter(iter);
let mut rng = small_rng();
v.shuffle(&mut rng);
rng.shuffle(&mut v);
v
}

Expand Down
79 changes: 64 additions & 15 deletions src/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ pub use self::mutable::MutableEntryKey;
pub use self::mutable::MutableKeys;
pub use self::raw_entry_v1::RawEntryApiV1;
pub use indexmap::map::{
Drain, IntoIter, IntoKeys, IntoValues, Iter, IterMut, IterMut2, Keys, Slice, Splice, Values,
ValuesMut,
Drain, ExtractIf, IntoIter, IntoKeys, IntoValues, Iter, IterMut, IterMut2, Keys, Slice, Splice,
Values, ValuesMut,
};

#[cfg(feature = "rayon")]
Expand Down Expand Up @@ -282,6 +282,55 @@ impl<K, V, S> OrderMap<K, V, S> {
self.inner.drain(range)
}

/// Creates an iterator which uses a closure to determine if an element should be removed,
/// for all elements in the given range.
///
/// If the closure returns true, the element is removed from the map and yielded.
/// If the closure returns false, or panics, the element remains in the map and will not be
/// yielded.
///
/// Note that `extract_if` lets you mutate every value in the filter closure, regardless of
/// whether you choose to keep or remove it.
///
/// The range may be any type that implements [`RangeBounds<usize>`],
/// including all of the `std::ops::Range*` types, or even a tuple pair of
/// `Bound` start and end values. To check the entire map, use `RangeFull`
/// like `map.extract_if(.., predicate)`.
///
/// If the returned `ExtractIf` is not exhausted, e.g. because it is dropped without iterating
/// or the iteration short-circuits, then the remaining elements will be retained.
/// Use [`retain`] with a negated predicate if you do not need the returned iterator.
///
/// [`retain`]: OrderMap::retain
///
/// ***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
///
/// Splitting a map into even and odd keys, reusing the original map:
///
/// ```
/// use ordermap::OrderMap;
///
/// let mut map: OrderMap<i32, i32> = (0..8).map(|x| (x, x)).collect();
/// let extracted: OrderMap<i32, i32> = map.extract_if(.., |k, _v| k % 2 == 0).collect();
///
/// let evens = extracted.keys().copied().collect::<Vec<_>>();
/// let odds = map.keys().copied().collect::<Vec<_>>();
///
/// assert_eq!(evens, vec![0, 2, 4, 6]);
/// assert_eq!(odds, vec![1, 3, 5, 7]);
/// ```
#[track_caller]
pub fn extract_if<F, R>(&mut self, range: R, pred: F) -> ExtractIf<'_, K, V, F>
where
F: FnMut(&K, &mut V) -> bool,
R: RangeBounds<usize>,
{
self.inner.extract_if(range, pred)
}

/// Splits the collection into two at the given index.
///
/// Returns a newly allocated map containing the elements in the range
Expand Down Expand Up @@ -1275,14 +1324,14 @@ impl<K, V, S> Index<usize> for OrderMap<K, V, S> {
///
/// ***Panics*** if `index` is out of bounds.
fn index(&self, index: usize) -> &V {
self.get_index(index)
.unwrap_or_else(|| {
panic!(
"index out of bounds: the len is {len} but the index is {index}",
len = self.len()
);
})
.1
if let Some((_, value)) = self.get_index(index) {
value
} else {
panic!(
"index out of bounds: the len is {len} but the index is {index}",
len = self.len()
);
}
}
}

Expand Down Expand Up @@ -1322,11 +1371,11 @@ impl<K, V, S> IndexMut<usize> for OrderMap<K, V, S> {
fn index_mut(&mut self, index: usize) -> &mut V {
let len: usize = self.len();

self.get_index_mut(index)
.unwrap_or_else(|| {
panic!("index out of bounds: the len is {len} but the index is {index}");
})
.1
if let Some((_, value)) = self.get_index_mut(index) {
value
} else {
panic!("index out of bounds: the len is {len} but the index is {index}");
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/map/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> {
/// ***Panics*** if the `other` index is out of bounds.
///
/// Computes in **O(1)** time (average).
#[track_caller]
pub fn swap_indices(self, other: usize) {
self.inner.swap_indices(other);
}
Expand Down Expand Up @@ -331,6 +332,7 @@ impl<'a, K, V> VacantEntry<'a, K, V> {
/// ***Panics*** if `index` is out of bounds.
///
/// Computes in **O(n)** time (average).
#[track_caller]
pub fn shift_insert(self, index: usize, value: V) -> &'a mut V {
self.inner.shift_insert(index, value)
}
Expand Down Expand Up @@ -462,6 +464,7 @@ impl<'a, K, V> IndexedEntry<'a, K, V> {
/// ***Panics*** if the `other` index is out of bounds.
///
/// Computes in **O(1)** time (average).
#[track_caller]
pub fn swap_indices(self, other: usize) {
self.inner.swap_indices(other)
}
Expand Down
Loading