I have a vector of strings.I need to convert it to HashMap.
Vector's 0 elements should become a key and 1 element should become a value. The same for 2, 3, and so on.
The obvious solution, just to make a for loop and add them to HashMap one by one. However, it will end up several lines of code. I am curious whether there is a cleaner, one-liner.
I know you can do vec.to_iter().collect(). However, this requires a vector to have tuples (vs a flat vector).
You can use chunks_exact plus a few combinators to achieve this. However, I wouldn't recommend putting this on only one line for readability reasons. This does have a downside, and that is extra elements (if the vector has an odd number of elements) will be discarded.
use std::collections::HashMap;
fn main() {
// vector with elements
let vector = vec!["a", "b", "c", "d", "e", "f"];
let map = vector.chunks_exact(2) // chunks_exact returns an iterator of slices
.map(|chunk| (chunk[0], chunk[1])) // map slices to tuples
.collect::<HashMap<_, _>>(); // collect into a hashmap
// outputs: Map {"e": "f", "c": "d", "a": "b"}
println!("Map {:?}", map);
}
slice::array_chunks is currently unstable but when it's stabilized in the future, I would prefer this over .chunks(2):
#![feature(array_chunks)]
use std::collections::HashMap;
fn main() {
let vec = vec![1, 2, 3, 4, 5, 6, 7];
let map = vec
.array_chunks::<2>()
.map(|[k, v]| (k, v))
.collect::<HashMap<_, _>>();
dbg!(map);
}
Output:
[src/main.rs:11] map = {
1: 2,
3: 4,
5: 6,
}
Playground
Using itertools's tuples:
use itertools::Itertools;
use std::collections::HashMap;
fn main() {
let v: Vec<String> = vec!["key1".into(), "val1".into(), "key2".into(), "val2".into()];
// Extra elements are discarded
let hm: HashMap<String, String> = v.into_iter().tuples().collect();
assert_eq!(hm, HashMap::from([("key1".into(), "val1".into()), ("key2".into(), "val2".into())]));
}
Related
I understand some of the jank involving iterables and arrays, but clearly not enough. I want to take any amount of iterables (vectors, arrays, slices, anything implementing IntoIterator) and provide an expected final size, and get an array (i.e. fixed-size) containing the chained values. To clarify, this is mostly for easy refactoring and function calling, so I want this utility to take ownership of the passed iterables and move all their contents into its output, such that:
let a1: u8 = [1, 2, 3];
let a2: u8 = [4, 5, 6];
let joined = join::<u8, 6>([a1, a2, ...]); // [u8; 6]
I tried implementing something with chain, but couldn't get it to work out. I know I can do this unsafely, but I'd rather avoid that if possible. Is there a way to do what I want?
My best (non-working) attempt:
fn join<T, C: IntoIterator<Item = T>, const N: usize>(iterables: Vec<C>) -> [T; N] {
let mut a = vec![].iter().chain(vec![]);
for iterable in iterables {
a = a.chain(iterable.into_iter());
}
a.collect().try_into().unwrap()
}
With credit to #SvenMarnach for simplifying, this problem is neatly solved like so:
use std::convert::TryInto;
fn join<T: Clone, const N: usize>(iterables: Vec<&[T]>) -> [T; N] {
let slice = iterables.concat();
let length = slice.len();
slice.try_into()
.unwrap_or_else(|_| panic!("joined has length {}, expected {}", length, N))
}
Used like so:
fn main() {
let params1 = [1, 2, 3];
let params2 = [4, 5];
print!("sum: {}", sum_six_numbers(join(vec![¶ms1, ¶ms2])));
}
fn sum_six_numbers(ns: [u8; 5]) -> u8 {
ns.iter().sum()
}
I am currently learning Rust, and I stumbled upon an operation for which I can find neither a standard implementation in std nor a reasonably formed snippet of code, which would do what I would like it to do.
Basically I would like to repeat each element of an iterator a given number of times. So for example if a had an iterator of [1,2,3], then by repeating each element 3 times for example I mean that output should be [1,1,1,2,2,2,3,3,3].
How would one do it idiomatically in Rust?
You can use repeat(n).take(n) to repeat the individual elements and flat_map to combine those repetitions into a flat iterator:
let it = vec![1, 2, 3].into_iter();
let repeated = it.flat_map(|n| std::iter::repeat(n).take(3));
assert!(repeated.collect::<Vec<_>>() == vec![1, 1, 1, 2, 2, 2, 3, 3, 3]);
A generic version that converts any iterator into a repeated iterator might look like this (playground):
fn repeat_element<T: Clone>(it: impl Iterator<Item = T>, cnt: usize) -> impl Iterator<Item = T> {
it.flat_map(move |n| std::iter::repeat(n).take(cnt))
}
I have two HashSet<u16>s and I would like to implement a = a U b. If possible, I'd like to use HashSet::union rather than loops or other tweaks.
I tried the following:
use std::collections::HashSet;
let mut a: HashSet<u16> = [1, 2, 3].iter().cloned().collect();
let b: HashSet<u16> = [7, 8, 9].iter().cloned().collect();
// I can build a union object that contains &u16
let union: HashSet<&u16> = a.union(&b).collect();
// But I can't store the union into a
a = a.union(&b).collect(); // -> compile error
// of course I can do
for x in &b {
a.insert(*x);
}
// but I wonder if union could not be used to simply build a union
The error message is the following:
the trait bound
`std::collections::HashSet<u16>: std::iter::FromIterator<&u16>`
is not satisfied
How can I perform a = a U b?
You don't want union — as you said, it will create a new HashSet. Instead you can use Extend::extend:
use std::collections::HashSet;
fn main() {
let mut a: HashSet<u16> = [1, 2, 3].iter().copied().collect();
let b: HashSet<u16> = [1, 3, 7, 8, 9].iter().copied().collect();
a.extend(&b);
println!("{:?}", a); // {8, 3, 2, 1, 7, 9}
}
(Playground)
Extend::extend is also implemented for other collections, e.g. Vec. The result for Vec will differ because Vec does not honor duplicates in the same way a Set does.
// But I can't store the union into a
a = a.union(&b).collect(); // -> compile error
The error message is the following:
the trait bound `std::collections::HashSet<u16>:
std::iter::FromIterator<&u16>` is not satisfied
It's because a is a HashSet<u16>, but a.union(&b) is an Iterator<Item=&u16>. Converting a.union(&b) to Iterator<Item=u16> by using .copied() works:
a = a.union(&b).copied().collect(); // compiles
As the other mentioned, this will create a new HashSet. In some cases, this might be what you want, but it will make more sense if it's assigned to another variable:
let c: HashSet<u16> = a.union(&b).copied().collect();
// a is unchanged here
For collecting multiple hashsets:
use std::collections::HashSet;
fn main() {
let a: HashSet<u16> = [1, 2, 3].iter().copied().collect();
let b: HashSet<u16> = [1, 3, 7, 8, 9].iter().copied().collect();
let all = [a,b];
let combined = all.iter().flatten().collect::<HashSet<_>>();
println!("{:?}", combined);
}
Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=44cd73eb3a4e628378cbb7ff11a32649
I've parsed a file, split the string by lines and want to leave only unique elements in each vector. I expect vec.dedup() to work like this:
let mut vec = vec!["a", "b", "a"];
vec.dedup();
assert_eq!(vec, ["a", "b"]);
But it fails:
thread 'main' panicked at 'assertion failed: `(left == right)`
left: `["a", "b", "a"]`,
right: `["a", "b"]`', src/main.rs:4:4
How can I remove duplicates?
As documented, Vec#dedup only removes consecutive elements from a vector (it is much cheaper than a full deduplication). It would work fine if the vector was vec!["a", "a", "b"], for example.
Of course, there are multiple potential solutions.
In order to obtain a vector with all duplicates removed while retaining the original order of the elements, the itertools crate provides a unique adaptor.
use itertools::Itertools;
let v = vec!["b", "a", "b"];
let v: Vec<_> = v.into_iter().unique().collect();
assert_eq!(v, ["b", "a"]);
If element order is not important, you may sort the elements first and then call dedupe.
let mut v = vec!["a", "b", "a"];
v.sort_unstable();
v.dedup();
assert_eq!(v, ["a", "b"]);
If fast element lookup is important, you may also consider using a set type instead, such as HashSet.
let v: HashSet<_> = ["a", "b", "a"].iter().cloned().collect();
let v2: HashSet<_> = ["b", "a"].iter().cloned().collect();
assert_eq!(v, v2);
The other answer points out that a HashSet is a better choice for a collection without duplicates, which I agree with. This shows how to directly deduplicate a Vec using that property of HashMap and without sorting the Vec first to use std::vec::Vec::dedup.
use std::hash::Hash;
use std::collections::HashSet;
fn dedup<T: Eq + Hash + Copy>(v: &mut Vec<T>) { // note the Copy constraint
let mut uniques = HashSet::new();
v.retain(|e| uniques.insert(*e));
}
fn main() {
let mut v = vec!["a", "b", "a"];
dedup(&mut v);
assert_eq!(&v, &vec!["a", "b"]);
}
This is a fast (O(n)) solution, but creating the HashSet requires some extra memory.
Is there any way to insert multiple entries into a HashMap at once in Rust? Or to initialize it with multiple entries? Anything other than manually calling insert on every single element you're inserting?
Edit for an example using English letter frequencies:
I basically want:
let frequencies = {
'a': 0.08167,
'b': 0.01492,
...
'z': 0.00074
}
I know I can achieve the same result by doing a for loop like the following, but I want to know if there is a way to do this without creating additional arrays and then looping over them, or a more elegant solution in general.
let mut frequencies = HashMap::new();
let letters = ['a','b','c', ...... 'z'];
let freqs = [0.08167, 0.01492, 0.02782, ......., 0.00074];
for i in 0..26 {
frequencies.insert(letters[i], freqs[i]);
}
For a literal, I could use the answer here, which will probably work fine for this example, but I'm curious whether there's a way to do this without it being a literal, in case this comes up in the future.
Is there any way to insert multiple entries into a HashMap at once in Rust?
Yes, you can extend a HashMap with values from an Iterator, like this:
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.extend((1..3).map(|n| (format!("{}*2=", n), n * 2)));
map.extend((7..9).map(|n| (format!("{}*2=", n), n * 2)));
println!("{:?}", map); // Prints {"1*2=": 2, "8*2=": 16, "7*2=": 14, "2*2=": 4}.
}
It is even a bit faster than calling the insert manually, because extend uses the size hint provided by the Iterator in order to reserve some space beforehand.
Check out the source code of the method here, in map.rs.
Or to initialize it with multiple entries?
This is possible as well, thanks to HashMap implementing the FromIterator trait. When a collection implements FromIterator, you can use the Iterator::collect shorthand to construct it. Consider the following examples, all of them generating the same map:
use std::collections::HashMap;
fn main() {
let mut map: HashMap<_, _> = (1..3).map(|n| (format!("{}*2=", n), n * 2)).collect();
map.extend((7..9).map(|n| (format!("{}*2=", n), n * 2)));
println!("{:?}", map); // Prints {"1*2=": 2, "8*2=": 16, "7*2=": 14, "2*2=": 4}.
}
use std::collections::HashMap;
fn main() {
let map: HashMap<_, _> = (1..3)
.chain(7..9)
.map(|n| (format!("{}*2=", n), n * 2))
.collect();
println!("{:?}", map); // Prints {"1*2=": 2, "8*2=": 16, "7*2=": 14, "2*2=": 4}.
}
use std::collections::HashMap;
use std::iter::FromIterator;
fn main() {
let iter = (1..3).chain(7..9).map(|n| (format!("{}*2=", n), n * 2));
let map = HashMap::<String, u32>::from_iter(iter);
println!("{:?}", map); // Prints {"1*2=": 2, "8*2=": 16, "7*2=": 14, "2*2=": 4}.
}
use std::collections::HashMap;
fn main() {
let pairs = [
("a", 1),
("b", 2),
("c", 3),
("z", 50),
];
println!("1. Insert multiple entries into a HashMap at once");
let mut map = HashMap::new();
map.extend(pairs);
println!("map: {map:#?}\n");
println!("2. Initialize with multiple entries");
let map = HashMap::from([
("a", 1),
("b", 2),
("c", 3),
("z", 50),
]);
println!("map: {map:#?}\n");
println!("3. Initialize with multiple entries");
let map = HashMap::from(pairs);
println!("map: {map:#?}\n");
println!("4. Initialize with multiple entries");
let map: HashMap<_, _> = pairs.into();
println!("map: {map:#?}");
}
See the Rust Playground.