-
Notifications
You must be signed in to change notification settings - Fork 158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature request: IndexMap::replace_key
#362
Comments
You can do it with use indexmap::map::MutableKeys;
map.get_full_mut2(&k).map(|(_i, key, _value)| *key = k); Or with the raw entry extension: use indexmap::map::RawEntryApiV1;
map.raw_entry_mut_v1().from_key(&k).and_modify(|key, _value| *key = k); For a more direct API, I would prefer to see this proposed and ultimately stabilized on the standard |
If the values of the new key and the old key are different (which also means that different hashes will be generated), can both approaches still be used normally? |
It would be an error (but still memory-safe) to overwrite a key with a different But as long as you encapsulate your own |
I still don't quite understand. During this process, I saw that generics |
Those opt-in mutability traits can change keys improperly, even aside from As for those generics, in the standard library,
In |
Ah, I understand now! Indeed, the correctness of keys in memory cannot be fully guaranteed due to the presence of interior mutability. I just need to ensure the correctness of the behavior of the method I encapsulate myself. Thank you for your answer! |
But I think we can keep this issue open. When the |
Um... I feel that there are still significant issues with this approach... Consider the following code: use indexmap::{map::MutableKeys, Equivalent, IndexMap};
use std::{
borrow::Borrow,
hash::{BuildHasher, Hash},
};
use thiserror::Error;
/// Error returned by `MapExt::replace_key`.
#[derive(Error, Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum ReplaceKeyErr {
#[error("replace_key: the old key does not exist")]
/// The old key does not exist.
OldKeyNotExist,
#[error("replace_key: the new key is already occupied")]
/// The new key is already occupied.
NewKeyOccupied,
}
/// Some general extensions to `Maps` (such as
/// [`HashMap`](https://doc.rust-lang.org/stable/std/collections/struct.HashMap.html),
/// [`BTreeMap`](https://doc.rust-lang.org/stable/std/collections/struct.BTreeMap.html),
/// [`IndexMap`](https://docs.rs/indexmap/latest/indexmap/map/struct.IndexMap.html)).
pub trait MapExt<K, Q: ?Sized = K> {
/// Replace an existing key with a new (non-existing) one.
///
/// If k1 does not exist, return `Err(ReplaceKeyErr::OldKeyNotExist)`.
///
/// Otherwise, if k2 and k1 are equal, do nothing and return `Ok(())`.
///
/// Otherwise, if k2 also exists, return `Err(ReplaceKeyErr::NewKeyOccupied)`.
///
/// Otherwise, return `Ok(())` after the replacement is completed.
fn replace_key(&mut self, k1: &Q, k2: K) -> Result<(), ReplaceKeyErr>
where
K: Borrow<Q>;
}
impl<K, Q, V, S> MapExt<K, Q> for IndexMap<K, V, S>
where
K: Borrow<Q>,
Q: ?Sized + Hash + Equivalent<K>,
S: BuildHasher,
{
fn replace_key(&mut self, k1: &Q, k2: K) -> Result<(), ReplaceKeyErr> {
if !self.contains_key(k1) {
return Err(ReplaceKeyErr::OldKeyNotExist);
}
if k1.equivalent(&k2) {
return Ok(());
}
if self.contains_key(k2.borrow()) {
return Err(ReplaceKeyErr::NewKeyOccupied);
}
// See: https://github.com/indexmap-rs/indexmap/issues/362
*self
.get_full_mut2(k1)
.expect("this should be unreachable")
.1 = k2;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn replace_key_indexmap() {
let mut map = indexmap::indexmap! {
"k1".to_string() => 123,
"k2".to_string() => 456
};
assert_eq!(map.replace_key("k1", "k3".to_string()), Ok(()));
assert_eq!(map["k3"], 123);
}
} The test failed with the following output:
The code on line 242 is: assert_eq!(map["k3"], 123); Is this approach unable to achieve the purpose of this test? Is there a better way to achieve it? |
I misunderstood that you do want to change the actual In that case, you should not use the mutability traits, but an actual new insertion. You can do that and get it into the right place like this: fn replace_key(&mut self, k1: &Q, k2: K) -> Result<(), ReplaceKeyErr> {
let Some(i) = self.get_index_of(k1) else {
return Err(ReplaceKeyErr::OldKeyNotExist);
};
if k1.equivalent(&k2) {
return Ok(());
}
if self.contains_key(&k2) {
return Err(ReplaceKeyErr::NewKeyOccupied);
}
// Note, this temporarily displaces the last entry into `i`,
// but we'll swap it back after we insert the new key.
let Some((_, v)) = self.swap_remove_index(i) else {
return Err(ReplaceKeyErr::OldKeyNotExist);
};
let (j, _) = self.insert_full(k2, v);
self.swap_indices(i, j);
Ok(())
} I don't think we expose anything that would let you do this without the swaps, but at least it's still O(1). |
Thanks! All tests have passed! 😊 |
The idea is simple: to replace an existing key with a new one and preserve its index position.
If I want to implement this feature externally, there seems to be no
O(1)
solution. It seems that only internal implementation can achieve this level of time complexity.The text was updated successfully, but these errors were encountered: