So I have a serde Value from somewhere and I would like to add additional "fields" to the Value before deserializing it afterwards. I'm looking through the docs but I don't see how to do it.
More specifically, let's say I have a HashMap whose values I want to merge into the Value.
pub fn merge(v: &Value, fields: &HashMap<String, String>) -> Value
You need to extract the Map (in the Value::Object case), copy it (as you're only taking an &Value input), update it, and re-wrap it in a Value::Object e.g.
pub fn merge(v: &Value, fields: &HashMap<String, String>) -> Value {
match v {
Value::Object(m) => {
let mut m = m.clone();
for (k, v) in fields {
m.insert(k.clone(), Value::String(v.clone()));
}
Value::Object(m)
}
v => v.clone(),
}
}
(on conflict this will override v's entries with fields's).
Related
Given the next collection:
let unmerged: Vec<T> = [... some T instances ...];
where T it's defined as follows:
pub struct T {
name: String,
other: HashMap<String, Other>
}
and Other:
pub struct Other {
name: String
}
I want to merge all the elements of type T that has the same value on it's name field into only once instance of T, and append the rest of the values on the HashMap of the rest of elements with the same value on the name field to that only unique instance, so finally, I would have something like:
let merged: Vec<T> = [
... some T instances ...
];
where every instances has a unique value on it's name field, and the HashMap contains all entries found on the elements of the unmerged collection where the name it's the same.
What would be the more effective aproach to solve this problem?
Thanks
The simplest way to approach this would be to build up a HashMap<String, T> and then collect the values into a Vec<T> at the end:
use std::collections::HashMap;
use std::collections::hash_map::Entry as HashMapEntry;
// Renamed from T, because T is more commonly used to denote a generic type.
struct Entry {
pub name: String,
pub other: HashMap<String, Other>,
}
struct Other {
pub name: String,
}
fn merge_duplicate_entries(it: impl IntoIterator<Item=Entry>) -> Vec<Entry> {
let mut result: HashMap<String, Entry> = HashMap::new();
for entry in it {
// We can't use and_modify/or_insert here since both will want to move
// all or part of entry, so we have to do this a bit more manually.
match result.entry(entry.name.clone()) {
HashMapEntry::Occupied(mut e) => {
e.get_mut().other.extend(entry.other.into_iter());
}
HashMapEntry::Vacant(e) => { e.insert(entry); }
}
}
result.into_values().collect()
}
This should be pretty straightforward. Note that when our result map already contains the name of an entry, we can use .extend() to dump the contents of Entry::other into the existing entry all at once. At the end, there will be exactly one Entry value for a given string, and we can collect the values into a vector.
Note that if there are two Entry values with the same name, and their respective other maps each have an entry with the same key, the second one encountered will overwrite the first.
I have a struct that contains various Routers. Mostly hashmaps. But for this specific hashmap, the values are not updating after insertion. There is no delete function. Just an insert function(shown below).
This is the main struct
pub struct Router {
....
web_socket_routes: Arc<RwLock<HashMap<String, HashMap<String, (PyFunction, u8)>>>>,
}
This is a getter
#[inline]
pub fn get_web_socket_map(
&self,
) -> &Arc<RwLock<HashMap<String, HashMap<String, (PyFunction, u8)>>>> {
&self.web_socket_routes
}
This is the insert method
pub fn add_websocket_route(
&mut self,
route: &str,
connect_route: (Py<PyAny>, bool, u8),
close_route: (Py<PyAny>, bool, u8),
message_route: (Py<PyAny>, bool, u8),
) {
let table = self.get_web_socket_map();
let (connect_route_function, connect_route_is_async, connect_route_params) = connect_route;
let (close_route_function, close_route_is_async, close_route_params) = close_route;
let (message_route_function, message_route_is_async, message_route_params) = message_route;
let insert_in_router =
|handler: Py<PyAny>, is_async: bool, number_of_params: u8, socket_type: &str| {
let function = if is_async {
PyFunction::CoRoutine(handler)
} else {
PyFunction::SyncFunction(handler)
};
let mut route_map = HashMap::new();
route_map.insert(socket_type.to_string(), (function, number_of_params));
println!("socket type is {:?} {:?}", table, route);
table.write().unwrap().insert(route.to_string(), route_map);
};
insert_in_router(
connect_route_function,
connect_route_is_async,
connect_route_params,
"connect",
);
insert_in_router(
close_route_function,
close_route_is_async,
close_route_params,
"close",
);
insert_in_router(
message_route_function,
message_route_is_async,
message_route_params,
"message",
);
}
After all the 3 insert_in_router calls, web_socket_routes only contains the insertion of the last insert_in_router call?
I have tried changing the Arc<RwLock< for a generic DashMap but I am still facing the same issues.
Why is this happening?
Your closure unconditionally creates a new inner hashmap each time, which it uses as value in the outer hashmap. However, it inserts it into the outer hashmap under the same key (route.to_string()) all three times, which results in each insert overwriting the previous one(s).
You need to implement a logic that will create a new inner hashmap only if one is missing under that key, otherwise look up the existing one. Then it should insert the value into the inner hashmap, either the freshly created one, or the one looked up. In Rust this is conveniently done using the entry API:
table
.write()
.unwrap()
.entry(route.to_string())
.or_default()
.insert(socket_type.to_string(), (function, number_of_params));
Rust's HashMap::insert
takes (K, V) and returns an (owned) V if an entry was already present. In this case, there should also be an (owned) key in the existing entry. Is there a way to get it?
i.e. is there a way to implement something like HashMap::insert_entry(&mut self, k: K, v: V) -> Option(K, V)>?
I see that you could do HashMap::remove_entry followed by HashMap::insert, but this requires two lookups. I believe this should only require one.
You can use HashMap::entry and its variants, along with the map_entry_replace feature:
#![feature(map_entry_replace)]
fn insert_entry<K: Hash + Eq, V>(m: &mut HashMap<K, V>, k: K, v: V) -> Option<(K, V)> {
match m.entry(k) {
Entry::Occupied(o) => {
Some(o.replace_entry(v))
}
Entry::Vacant(va) => {
va.insert(v);
None
}
}
}
playground link
I have a collection of Foo.
struct Foo {
k: String,
v: String,
}
I want a HashMap which has the key &foo.k and the value foo.
Apparently, it is not possible without redesigning Foo by introducing Rc or clone/copy the k.
fn t1() {
let foo = Foo { k: "k".to_string(), v: "v".to_string() };
let mut a: HashMap<&str, Foo> = HashMap::new();
a.insert(&foo.k, foo); // Error
}
There seems to be a workaround by abusing get() from HashSet (Playground):
use std::collections::{HashMap, HashSet};
use std::hash::{Hash, Hasher, BuildHasher};
use std::collections::hash_map::Entry::*;
struct Foo {
k: String,
v: String,
}
impl PartialEq for Foo {
fn eq(&self, other: &Self) -> bool { self.k == other.k }
}
impl Eq for Foo {}
impl Hash for Foo {
fn hash<H: Hasher>(&self, h: &mut H) { self.k.hash(h); }
}
impl ::std::borrow::Borrow<str> for Foo {
fn borrow(&self) -> &str {
self.k.as_str()
}
}
fn t2() {
let foo = Foo { k: "k".to_string(), v: "v".to_string() };
let mut a: HashSet<Foo> = HashSet::new();
a.insert(foo);
let bar = Foo { k: "k".to_string(), v: "v".to_string() };
let foo = a.get("k").unwrap();
println!("{}", foo.v);
}
This is pretty tedious. What if a Foo has multiple fields and different collections of Foo to key on different fields?
Apparently, it is not possible without redesigning Foo by introducing Rc or clone/copy the k.
That's correct, it is not possible to have HashMap<&K, V> where the key points to some component of the value.
The HashMap owns the key and the value, conceptually storing both in big vectors. When a new value is added to the HashMap, these existing values might need to be moved around due to hash collisions or the vectors might need to be reallocated to hold more items. Both of these operations would invalidate the address of any existing key, leaving it pointing at invalid memory. This would break Rust's safety guarantees, thus it is disallowed.
Read Why can't I store a value and a reference to that value in the same struct? for a thorough discussion.
Additionally, trentcl points out that HashMap::get_mut would allow you to get a mutable reference to the key, which would allow you to change the key without the map knowing. As the documentation states:
It is a logic error for a key to be modified in such a way that the key's hash, as determined by the Hash trait, or its equality, as determined by the Eq trait, changes while it is in the map.
Workarounds include:
Remove the key from the struct and store it separately. Instead of HashMap<&K, V> where V is (K, Data), store HashMap<K, Data>. You can return a struct which glues references to the key and value together (example)
Share ownership of the key using Rc (example)
Create duplicate keys using Clone or Copy.
Use a HashSet as you have done, enhanced by Sebastian Redl's suggestion. A HashSet<K> is actually just a HashMap<K, ()>, so this works by transferring all ownership to the key.
You can introduce a wrapper type for the item stored in the set.
struct FooByK(Foo);
Then implement the various traits needed for the set for this struct instead. This lets you choose a different wrapper type if you need a set that indexes by a different member.
I can't find a suitable way to return the exact value of key in a HashMap in Rust . All the existing get methods return in a different format rather than the exact format.
You probably want the HashMap::remove method - it deletes the key from the map and returns the original value rather than a reference:
use std::collections::HashMap;
struct Thing {
content: String,
}
fn main() {
let mut hm: HashMap<u32, Thing> = HashMap::new();
hm.insert(
123,
Thing {
content: "abc".into(),
},
);
hm.insert(
432,
Thing {
content: "def".into(),
},
);
// Remove object from map, and take ownership of it
let value = hm.remove(&432);
if let Some(v) = value {
println!("Took ownership of Thing with content {:?}", v.content);
};
}
The get methods must return a reference to the object because the original object can only exist in one place (it is owned by the HashMap). The remove method can return the original object (i.e "take ownership") only because it removes it from its original owner.
Another solution, depending on the specific situation, may be to take the reference, call .clone() on it to make a new copy of the object (in this case it wouldn't work because Clone isn't implemented for our Thing example object - but it would work if the value way, say, a String)
Finally it may be worth noting you can still use the reference to the object in many circumstances - e.g the previous example could be done by getting a reference:
use std::collections::HashMap;
struct Thing {
content: String,
}
fn main() {
let mut hm: HashMap<u32, Thing> = HashMap::new();
hm.insert(
123,
Thing {
content: "abc".into(),
},
);
hm.insert(
432,
Thing {
content: "def".into(),
},
);
let value = hm.get(&432); // Get reference to the Thing containing "def" instead of removing it from the map and taking ownership
// Print the `content` as in previous example.
if let Some(v) = value {
println!("Showing content of referenced Thing: {:?}", v.content);
}
}
There are two basic methods of obtaining the value for the given key: get() and get_mut(). Use the first one if you just want to read the value, and the second one if you need to modify the value:
fn get(&self, k: &Q) -> Option<&V>
fn get_mut(&mut self, k: &Q) -> Option<&mut V>
As you can see from their signatures, both of these methods return Option rather than a direct value. The reason is that there may be no value associated to the given key:
use std::collections::HashMap;
let mut map = HashMap::new();
map.insert(1, "a");
assert_eq!(map.get(&1), Some(&"a")); // key exists
assert_eq!(map.get(&2), None); // key does not exist
If you are sure that the map contains the given key, you can use unwrap() to get the value out of the option:
assert_eq!(map.get(&1).unwrap(), &"a");
However, in general, it is better (and safer) to consider also the case when the key might not exist. For example, you may use pattern matching:
if let Some(value) = map.get(&1) {
assert_eq!(value, &"a");
} else {
// There is no value associated to the given key.
}