I'm trying to get the keys of a BTreeMap with u32 keys.
When I use the .iter().keys() method, it returns a reference to the keys: &u32.
I understand the logic behind getting a reference to the key because it doesn't consume the data structure, but since u32 implements the Copy trait, I figured it is possible to get u32 directly.
The only way I found of doing this is by mapping over all the keys and dereferencing them:
let map = BTreeMap::from([
(0, "foo"),
(1, "bar"),
(2, "baz")
])
let keys: Vec<u32> = map.iter().map(|(k, _)| *k).collect();
Is there a better, faster or more concise way of doing this?
I suggest either
let keys = map.keys().copied().collect();
or if you don't need the map any more:
let keys = map.into_keys().collect();
Related
Given a HashMap of n elements how does one start iteration from n-x element.
The order of elements does not matter, the only problem I need to solve is to start iteration from given key.
Example:
let mut map: HashMap<&str, i32> = HashMap::new();
map.insert("one", 1);
map.insert("two", 2);
map.insert("three", 3);
map.insert("four", 4);
[...]
for (k, v) in map {
//how to start iteration from third item and not the first one
}
Tried to google it but no examples found so far.
Tried to google it but no examples found so far.
That's because as Chayim Friedman notes it doesn't really make sense, a hashmap has an essentially random internal order, which means it has an arbitrary iteration order. Iterating from or between keys (/ entries) thus doesn't make much sense.
So it sounds a lot like an XY problem, what is the reason why you're trying to iterate "starting from a given key"?
Though if you really want that, you can just use the skip_while adapter, and skip while you have not found the key you're looking for.
Alternatively, since your post is ambiguous (you talk about both key and position) you can use the skip adapter to skip over a fixed number of items.
Technically neither will start iterating from that entry, they'll both start iterating from 0 but will only yield items following the specified break point. The standard library's hashmap has no support for range iteration (because that doesn't really make any sense on hashmap), and its iterators are not random access either (for similar reason).
You may want to use a BTreeMap, which has sorted keys and a range function which iterates over a range of keys.
use std::collections::BTreeMap;
fn main() {
let mut map = BTreeMap::new();
map.insert(1, "one");
map.insert(2, "two");
map.insert(3, "three");
for (&key, &value) in map.range(2..) {
println!("{key}: {value}");
}
}
// 2: two
// 3: three
I'm trying this, but doesn't work:
let map = HashMap::new();
map.insert(1, "aaa");
map.insert(2, "bbb");
let a = map.counts_by(|k, v| v.starts_with("a"));
What is the right way?
Anything that iterates over collections in Rust is going to factor through the Iterator API, and unlike in Java where iterators are often implicitly used, it's very common in Rust to explicitly ask for an iterator (with .iter()) and do some work directly on it in a functional style. In your case, there are three things we need to do here.
Get the values of the HashMap. This can be done with the values method, which returns an iterator.
Keep only the ones satisfying a particular predicate. This is a filter operation and will produce another iterator. Note that this does not yet iterate over the hash map; it merely produces another iterator capable of doing so later.
Count the matches, using count.
Putting it all together, we have
map.values().filter(|v| v.starts_with("a")).count()
You should filter an iterator of the HashMap, then count the elements of the iterator:
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, "aaa");
map.insert(2, "bbb");
assert_eq!(
map.iter().filter(|(_k, v)| v.starts_with("a")).count(),
1
);
}
Notice that the map also has to be marked as mut in order to insert new elements, and the filter closure destructures into a tuple containing the key and the value, rather than accepting two separate parameters.
I have two HashMaps (playground):
let mut m1: HashMap<u8, usize, _> = HashMap::new();
m1.insert(1, 100);
m1.insert(2, 200);
let mut m2: HashMap<u8, usize, _> = HashMap::new();
m2.insert(2, 200);
m2.insert(1, 100);
How can I check if the two maps m1 and m2 are identical?
By "identical", I mean all of the following conditions are satisfied.
Type of keys is same.
Type of values is same.
Two maps have exactly the same key set. Insertion order shall not matter.
Two maps have exactly the same value for every key (i.e. m1.get(k) == m2.get(k) for every existing key k).
As far as I tested, just m1 == m2 works. However, it this behavior guaranteed? I want a some sort of guarantee (thus I added #language-lawyer tag).
I've already read the official documentation of HashMap.
Also, what about HashSet and Vec? (I've also read their documentation.)
Looking through the source of the std libraries you can find the implementation of PartialEq for those different collections:
HashMap iterate over all key/value pair and check if the other map has a corresponding entry for that key, and then check if those value are equal: source.
HashSet iterate of the keys and check if the other set contains that key: source.
Vec actually call eq on the underlying slice, which either iterate across every values and compare them: source or does a bitwise comparaison if the type allows it by calling memcmp: source.
I don't know if there is any kind of garanties that this behavior will never change, but being stables, widely use APIs, I don't see them change, ever.
First, about joining the keys unordered. I found a few similar topics (like this one, which is for &str keys though) and the simplest solution I could come up with was this:
let s = my_hash_map.keys().map(|n| n.to_string()).collect::<Vec<_>>().join(",");
Is there a simpler way to do it?
Also, I would often prefer the elements in the resulting string to be sorted, but from what I've seen the standard library still doesn't provide a sorted() method I could simply plug into the above chain (as we normally would in Python, for example). So is there some other standard (with no external crates) simple way we could do that in a one-liner in Rust?
Well, instead of a "oneliner" each time, just create a funtion and call that "oneliner" function each time:
fn join_sorted(strs: impl Iterator<Item = String>, separator: &str) -> String {
let mut v: Vec<_> = strs.collect();
v.sort();
v.join(separator)
}
Playground
Being fairly new to Rust, I was wondering on how to create a HashMap with a default value for a key? For example, having a default value 0 for any key inserted in the HashMap.
In Rust, I know this creates an empty HashMap:
let mut mymap: HashMap<char, usize> = HashMap::new();
I am looking to maintain a counter for a set of keys, for which one way to go about it seems to be:
for ch in "AABCCDDD".chars() {
mymap.insert(ch, 0)
}
Is there a way to do it in a much better way in Rust, maybe something equivalent to what Ruby provides:
mymap = Hash.new(0)
mymap["b"] = 1
mymap["a"] # 0
Answering the problem you have...
I am looking to maintain a counter for a set of keys.
Then you want to look at How to lookup from and insert into a HashMap efficiently?. Hint: *map.entry(key).or_insert(0) += 1
Answering the question you asked...
How does one create a HashMap with a default value in Rust?
No, HashMaps do not have a place to store a default. Doing so would cause every user of that data structure to allocate space to store it, which would be a waste. You'd also have to handle the case where there is no appropriate default, or when a default cannot be easily created.
Instead, you can look up a value using HashMap::get and provide a default if it's missing using Option::unwrap_or:
use std::collections::HashMap;
fn main() {
let mut map: HashMap<char, usize> = HashMap::new();
map.insert('a', 42);
let a = map.get(&'a').cloned().unwrap_or(0);
let b = map.get(&'b').cloned().unwrap_or(0);
println!("{}, {}", a, b); // 42, 0
}
If unwrap_or doesn't work for your case, there are several similar functions that might:
Option::unwrap_or_else
Option::map_or
Option::map_or_else
Of course, you are welcome to wrap this in a function or a data structure to provide a nicer API.
ArtemGr brings up an interesting point:
in C++ there's a notion of a map inserting a default value when a key is accessed. That always seemed a bit leaky though: what if the type doesn't have a default? Rust is less demanding on the mapped types and more explicit about the presence (or absence) of a key.
Rust adds an additional wrinkle to this. Actually inserting a value would require that simply getting a value can also change the HashMap. This would invalidate any existing references to values in the HashMap, as a reallocation might be required. Thus you'd no longer be able to get references to two values at the same time! That would be very restrictive.
What about using entry to get an element from the HashMap, and then modify it.
From the docs:
fn entry(&mut self, key: K) -> Entry<K, V>
Gets the given key's corresponding entry in the map for in-place
manipulation.
example
use std::collections::HashMap;
let mut letters = HashMap::new();
for ch in "a short treatise on fungi".chars() {
let counter = letters.entry(ch).or_insert(0);
*counter += 1;
}
assert_eq!(letters[&'s'], 2);
assert_eq!(letters[&'t'], 3);
assert_eq!(letters[&'u'], 1);
assert_eq!(letters.get(&'y'), None);
.or_insert() and .or_insert_with()
Adding to the existing example for .entry().or_insert(), I wanted to mention that if the default value passed to .or_insert() is dynamically generated, it's better to use .or_insert_with().
Using .or_insert_with() as below, the default value is not generated if the key already exists. It only gets created when necessary.
for v in 0..s.len() {
components.entry(unions.get_root(v))
.or_insert_with(|| vec![]) // vec only created if needed.
.push(v);
}
In the snipped below, the default vector passed to .or_insert() is generated on every call. If the key exists, a vector is being created and then disposed of, which can be wasteful.
components.entry(unions.get_root(v))
.or_insert(vec![]) // vec always created.
.push(v);
So for fixed values that don't have much creation overhead, use .or_insert(), and for values that have appreciable creation overhead, use .or_insert_with().
A way to start a map with initial values is to construct the map from a vector of tuples. For instance, considering, the code below:
let map = vec![("field1".to_string(), value1), ("field2".to_string(), value2)].into_iter().collect::<HashMap<_, _>>();