I am trying to accomplish something rather simple, but not sure how to do it in Rust.
I have a Vec<&Vec>, something like the below example.
[
["a1", "b2", "c3"],
["d1", "e2", "f3"],
["g1", "h2", "i3"]
]
I want to push an additional string at the end of each vector.
[
["a1", "b2", "c3", "something"],
["d1", "e2", "f3", "something"],
["g1", "h2", "i3", "something"]
]
What I've tried so far is below:
vec_of_strings
.iter_mut()
.map(|x| x.clone().push("something".to_string()))
.collect::<Vec<_>>();
println!("{:?}", vec_of_strings);
But the output is showing that nothing is appended.
What you're doing creates a new Vec, it does not modify the exist ones. Indeed, the existing ones cannot be modified as you are borrowing them immutably (the & in Vec<&Vec<_>>).
Note that using .iter_mut() instead of .iter() is pointless here as you aren't mutating the elements.
Additionally, Vec::push() doesn't return anything, so the .to_string() invocation should be giving you a compile-time error. (I assume you meant to call this on the string literal instead.)
Fixing the above issues:
let new_vec_of_strings = vec_of_strings
.iter()
.map(|x| {
let mut x = x.clone();
x.push("something".to_string());
x
})
.collect::<Vec<_>>();
println!("{:?}", new_vec_of_strings);
However, this all seems like an XY problem -- there is probably a better way to accomplish whatever your goal is.
If I understand correctly, You need to return a vec in map.
fn main() {
let mut vec_of_strings = vec![
vec!["a1", "b2", "c3"],
vec!["d1", "e2", "f3"],
vec!["g1", "h2", "i3"]
];
println!("{:?}", vec_of_strings);
let vec_of_strings: Vec<Vec<&str>> = vec_of_strings.iter_mut().map(|x| {x.push("something"); x.clone()}).collect();
println!("{:?}", vec_of_strings);
}
Rust Playground
I think there's a misunderstanding on what the map method was made for. This methodis generally used for data transformations where the original values remain unchanged. What you are really doing here is causing a side effect, and the map method does not help you at all here.
Just use a for loop. It's not like you're saving keystrokes by using map and interators.
However, you mentioned you have a Vec<&Vec>. Having this type seems unfit for your purpose. Cloning the entire vec just to add 1 element is terrible for performance.
I see 2 choices: either have it fully owned, i.e. Vec<Vec>, or just make the inner Vecs mutable, as in Vec<&mut Vec>.
This is the first option, and I think this is the most idiomatic:
fn main() {
let mut vec_of_strings = vec![
vec!["a1", "b2", "c3"],
vec!["d1", "e2", "f3"],
vec!["g1", "h2", "i3"]
];
for vec in vec_of_strings.iter_mut() {
vec.push("something");
}
println!("{vec_of_strings:?}");
}
If having it in an owned form is not acceptable, then another option is to use Vec<&mut Vec>:
fn main() {
fn main() {
let mut vec_of_strings = vec![
vec!["a1", "b2", "c3"],
vec!["d1", "e2", "f3"],
vec!["g1", "h2", "i3"]
];
//suppose somehow a function gave you this:
let vec_of_mut_strings: Vec<&mut Vec<_>> = vec_of_strings
.iter_mut()
.collect();
for vec in vec_of_mut_strings {
vec.push("something");
}
//notice that the original vec_of_strings change
println!("{vec_of_strings:?}");
}
}
I assume your output looks like this:
[(), (), ()]
Here are some suggestions:
Prefer using for_each for mutations instead of map:
vec_of_strings
.iter_mut()
.for_each(|x| {
x.push("something");
});
println!("{:?}", vec_of_strings);
Note that this assumes that the vec_of_strings is defined similarly as such:
let mut vec1 = vec!["a1", "b2", "c3"];
let mut vec2 = vec!["d1", "e2", "f3"];
let mut vec3 = vec!["g1", "h2", "i3"];
let mut vec_of_strings: Vec<&mut Vec<&str>> = vec![&mut vec1, &mut vec2, &mut vec3];
Playground
The function in .map in your example doesn't return anything because Vec::push doesn't return the vector.
Separate x to another line to return the vector.
vec_of_strings.iter_mut()
.map(|x| {
x.push("something");
x
})
.collect::<Vec<&str>>();
Note that this assumes that the vec_of_strings is defined similarly as such:
let mut vec1 = vec!["a1", "b2", "c3"];
let mut vec2 = vec!["d1", "e2", "f3"];
let mut vec3 = vec!["g1", "h2", "i3"];
let mut vec_of_strings: Vec<&mut Vec<&str>> = vec![&mut vec1, &mut vec2, &mut vec3];
Playground
You might also like to be more explicit with the type of the vector elements (&str) when using map:
.collect::<Vec<&str>>();
instead of
.collect::<Vec<_>>();
which was inferred as Vec<()> by the compiler because of the return type of Vec::push in map (hence the output).
A solution that keeps the original format extending each array by one entry:
vec_of_strings
.iter_mut()
.map(|a| {
let mut v = a.to_vec();
v.push("something");
let arr: [&str; 4] = v.try_into().unwrap();
arr
})
.collect::<Vec<[&str; 4]>>();
Playground
Related
So, I have the following code successfully performing filter in vector:
let mut v1 : Vec<i32> = vec!(1,2,3);
let v2 : Vec<&mut i32> = v1.iter_mut().filter(|x| {**x == 2}).collect();
println!("{:?}", v2);
Since the type signature of the predicate in the filter function is
FnMut(&Self::Item) -> bool, I was assuming that that mutation inside
the closure will work:
let mut v1 : Vec<i32> = vec!(1,2,3);
let v2 : Vec<&mut i32> = v1.iter_mut().filter(|x| {**x = 3; **x == 2}).collect();
println!("{:?}", v2);
But the above code results in a compile error. How to fix that ? Note
that I'm playing with rust to get a better understanding, so the abpve
example doesn't make sense (usually, nobody will try to mutate
things inside filter).
You are confusing two concepts: FnMut means that a function can change its captured variables, like:
fn main() {
let v1 = vec![1, 2, 3];
let mut i = 0usize;
let v2: Vec<_> = v1
.into_iter()
.filter(|x| {
i = i + 1;
*x == 2
})
.collect();
println!("We iterate {} times and produce {:?}", i, v2);
}
This doesn't mean that every parameter of a function will be mutable.
In your code, filter() takes a &Self::Item, which is very different from the map() one that takes Self::Item. Because the real type will translate to Map<Item=&mut i32> and Filter<Item=&&mut i32>. Rust forbids you from mutating a reference if it's behind a non mutable reference:
fn test(a: &&mut i32) {
**a = 5;
}
error[E0594]: cannot assign to `**a` which is behind a `&` reference
This is because Rust follows the the-rules-of-references:
At any given time, you can have either one mutable reference or any number of immutable references.
References must always be valid.
This means you can have more than one &&mut but only one &mut &mut. If Rust didn't stop you, you could mutate a &&mut and that would poison any other &&mut.
Unfortunately the full error description of E0594 is still not available, see #61137.
Note: Avoid side effects when you use the iterator API, I think it's OK to mutate your FnMut state but not the item, you should do this in a for loop, like:
fn main() {
let mut v1 = vec![1, 2, 3];
for x in v1.iter_mut().filter(|x| **x == 2) {
*x = 1;
}
println!("{:?}", v1);
}
How would one translate the following Python, in which several files are read and their contents are used as values to a dictionary (with filename as key), to Rust?
countries = {region: open("{}.txt".format(region)).read() for region in ["canada", "usa", "mexico"]}
My attempt is shown below, but I was wondering if a one-line, idiomatic solution is possible.
use std::{
fs::File,
io::{prelude::*, BufReader},
path::Path,
collections::HashMap,
};
macro_rules! map(
{ $($key:expr => $value:expr),+ } => {
{
let mut m = HashMap::new();
$(
m.insert($key, $value);
)+
m
}
};
);
fn lines_from_file<P>(filename: P) -> Vec<String>
where
P: AsRef<Path>,
{
let file = File::open(filename).expect("no such file");
let buf = BufReader::new(file);
buf.lines()
.map(|l| l.expect("Could not parse line"))
.collect()
}
fn main() {
let _countries = map!{ "canada" => lines_from_file("canada.txt"),
"usa" => lines_from_file("usa.txt"),
"mexico" => lines_from_file("mexico.txt") };
}
Rust's iterators have map/filter/collect methods which are enough to do anything Python's comprehensions can. You can create a HashMap with collect on an iterator of pairs, but collect can return various types of collections, so you may have to specify the type you want.
For example,
use std::collections::HashMap;
fn main() {
println!(
"{:?}",
(1..5).map(|i| (i + i, i * i)).collect::<HashMap<_, _>>()
);
}
Is roughly equivalent to the Python
print({i+i: i*i for i in range(1, 5)})
But translated very literally, it's actually closer to
from builtins import dict
def main():
print("{!r}".format(dict(map(lambda i: (i+i, i*i), range(1, 5)))))
if __name__ == "__main__":
main()
not that you would ever say it that way in Python.
Python's comprehensions are just sugar for a for loop and accumulator. Rust has macros--you can make any sugar you want.
Take this simple Python example,
print({i+i: i*i for i in range(1, 5)})
You could easily re-write this as a loop and accumulator:
map = {}
for i in range(1, 5):
map[i+i] = i*i
print(map)
You could do it basically the same way in Rust.
use std::collections::HashMap;
fn main() {
let mut hm = HashMap::new();
for i in 1..5 {
hm.insert(i + i, i * i);
}
println!("{:?}", hm);
}
You can use a macro to do the rewriting to this form for you.
use std::collections::HashMap;
macro_rules! hashcomp {
($name:ident = $k:expr => $v:expr; for $i:ident in $itr:expr) => {
let mut $name = HashMap::new();
for $i in $itr {
$name.insert($k, $v);
}
};
}
When you use it, the resulting code is much more compact. And this choice of separator tokens makes it resemble the Python.
fn main() {
hashcomp!(hm = i+i => i*i; for i in 1..5);
println!("{:?}", hm);
}
This is just a basic example that can handle a single loop. Python's comprehensions also can have filters and additional loops, but a more advanced macro could probably do that too.
Without using your own macros I think the closest to
countries = {region: open("{}.txt".format(region)).read() for region in ["canada", "usa", "mexico"]}
in Rust would be
let countries: HashMap<_, _> = ["canada", "usa", "mexico"].iter().map(|&c| {(c,read_to_string(c.to_owned() + ".txt").expect("Error reading file"),)}).collect();
but running a formatter, will make it more readable:
let countries: HashMap<_, _> = ["canada", "usa", "mexico"]
.iter()
.map(|&c| {
(
c,
read_to_string(c.to_owned() + ".txt").expect("Error reading file"),
)
})
.collect();
A few notes:
To map a vector, you need to transform it into an iterator, thus iter().map(...).
To transform an iterator back into a tangible data structure, e.g. a HashMap (dict), use .collect(). This is the advantage and pain of Rust, it is very strict with types, no unexpected conversions.
A complete test program:
use std::collections::HashMap;
use std::fs::{read_to_string, File};
use std::io::Write;
fn create_files() -> std::io::Result<()> {
let regios = [
("canada", "Ottawa"),
("usa", "Washington"),
("mexico", "Mexico city"),
];
for (country, capital) in regios {
let mut file = File::create(country.to_owned() + ".txt")?;
file.write_fmt(format_args!("The capital of {} is {}", country, capital))?;
}
Ok(())
}
fn create_hashmap() -> HashMap<&'static str, String> {
let countries = ["canada", "usa", "mexico"]
.iter()
.map(|&c| {
(
c,
read_to_string(c.to_owned() + ".txt").expect("Error reading file"),
)
})
.collect();
countries
}
fn main() -> std::io::Result<()> {
println!("Hello, world!");
create_files().expect("Failed to create files");
let countries = create_hashmap();
{
println!("{:#?}", countries);
}
std::io::Result::Ok(())
}
Not that specifying the type of countries is not needed here, because the return type of create_hashmap() is defined.
I'm trying to create a petgraph Graph from JSON data. The JSON contains the edges of the graph, the key represents the starting vertex and the value is a list of adjacent vertices. It's possible to generate a graph with a vector of edges.
I managed to create a Vec<(String, String))> but not a Vec<(&str, &str)> as expected.
extern crate petgraph;
extern crate serde_json;
use petgraph::prelude::*;
use serde_json::{Value, Error};
fn main() {
let data = r#"{
"A": [ "B" ],
"B": [ "C", "D" ],
"D": [ "E", "F" ]
}"#;
let json_value: Value = serde_json::from_str(data).unwrap();
let mut edges: Vec<(String, String)> = vec![];
if let Value::Object(map) = json_value {
for (from_edge, array) in &map {
if let &Value::Array(ref array_value) = array {
for edge in array_value {
if let &Value::String(ref to_edge) = edge {
edges.push((from_edge.clone(), to_edge.clone()))
}
}
}
}
}
// let graph = DiGraphMap::<&str, ()>::from_edges(edges);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct
// `std::string::String`, found &str
}
I tried different things:
Change the graph type to DiGraphMap::<String, ()>, however it does not accept it.
Transform a Vec<(String, String)> into a Vec<(&str, &str)>. I read this post but it didn't help.
edges.push((&"a", &"b")) works but not edges.push((&from.clone(), &to.clone())).
There is probably a better way to extract the edges here.
Change the graph type to DiGraphMap::<String, ()>, however it does not accept it.
A GraphMap requires that the node type be copyable. String does not implement Copy.
Transform a Vec<(String, String)> into a Vec<(&str, &str)>
As mentioned in the question you linked, this is impossible. What you can do is create a second Vec with &str that reference the original Strings:
let a: Vec<(String, String)> = vec![("a".into(), "b".into())];
let b: Vec<(&str, &str)> = a.iter()
.map(|&(ref x, ref y)| (x.as_str(), y.as_str()))
.collect();
However, that's not needed in this case. Instead, read the JSON data into a data structure that models a map (I chose BTreeMap) and leave the Strings there. You can then construct an iterator of pairs of references to those Strings, building the graph from that:
extern crate petgraph;
extern crate serde_json;
use petgraph::prelude::*;
use std::collections::BTreeMap;
use std::iter;
fn main() {
let data = r#"{
"A": [ "B" ],
"B": [ "C", "D" ],
"D": [ "E", "F" ]
}"#;
let json_value: BTreeMap<String, Vec<String>> =
serde_json::from_str(data).unwrap();
let edges = json_value
.iter()
.flat_map(|(k, vs)| {
let vs = vs.iter().map(|v| v.as_str());
iter::repeat(k.as_str()).zip(vs)
});
let graph: DiGraphMap<_, ()> = edges.collect();
}
I need to encapsulate this into a function
This is barely possible to do. Since JSON strings contain UTF-8 data, Serde allows you to get references to original input strings. You need to remember that your graph cannot outlive input:
fn main() {
let data = r#"{
"A": [ "B" ],
"B": [ "C", "D" ],
"D": [ "E", "F" ]
}"#;
let graph = example(data);
}
fn example(data: &str) -> serde_json::Result<DiGraphMap<&str, ()>> {
let json_value: BTreeMap<&str, Vec<&str>> = serde_json::from_str(data)?;
let edges = json_value
.into_iter()
.flat_map(|(k, vs)| iter::repeat(k).zip(vs));
Ok(edges.collect())
}
Suppose I have a HashMap and I want to get a mutable reference to an entry, or if that entry does not exist I want a mutable reference to a new object, how can I do it? I've tried using unwrap_or(), something like this:
fn foo() {
let mut map: HashMap<&str, Vec<&str>> = HashMap::new();
let mut ref = map.get_mut("whatever").unwrap_or( &mut Vec::<&str>::new() );
// Modify ref.
}
But that doesn't work because the lifetime of the Vec isn't long enough. Is there any way to tell Rust that I want the returned Vec to have the same lifetime as foo()? I mean there is this obvious solution but I feel like there should be a better way:
fn foo() {
let mut map: HashMap<&str, Vec<&str>> = HashMap::new();
let mut dummy: Vec<&str> = Vec::new();
let mut ref = map.get_mut("whatever").unwrap_or( &dummy );
// Modify ref.
}
As mentioned by Shepmaster, here is an example of using the entry pattern. It seems verbose at first, but this avoids allocating an array you might not use unless you need it. I'm sure you could make a generic function around this to cut down on the chatter :)
use std::collections::HashMap;
use std::collections::hash_map::Entry::{Occupied, Vacant};
fn foo() {
let mut map = HashMap::<&str, Vec<&str>>::new();
let mut result = match map.entry("whatever") {
Vacant(entry) => entry.insert(Vec::new()),
Occupied(entry) => entry.into_mut(),
};
// Do the work
result.push("One thing");
result.push("Then another");
}
This can also be shortened to or_insert as I just discovered!
use std::collections::HashMap;
fn foo() {
let mut map = HashMap::<&str, Vec<&str>>::new();
let mut result = map.entry("whatever").or_insert(Vec::new());
// Do the work
result.push("One thing");
result.push("Then another");
}
If you want to add your dummy into the map, then this is a duplicate of How to properly use HashMap::entry? or Want to add to HashMap using pattern match, get borrow mutable more than once at a time (or any question about the entry API).
If you don't want to add it, then your code is fine, you just need to follow the compiler error messages to fix it. You are trying to use a keyword as an identifier (ref), and you need to get a mutable reference to dummy (& mut dummy):
use std::collections::HashMap;
fn foo() {
let mut map: HashMap<&str, Vec<&str>> = HashMap::new();
let mut dummy: Vec<&str> = Vec::new();
let f = map.get_mut("whatever").unwrap_or( &mut dummy );
}
fn main() {}
I'm trying to join strings in a vector into a single string, in reverse from their order in the vector. The following works:
let v = vec!["a".to_string(), "b".to_string(), "c".to_string()];
v.iter().rev().map(|s| s.clone()).collect::<Vec<String>>().connect(".")
However, this ends up creating a temporary vector that I don't actually need. Is it possible to do this without a collect? I see that connect is a StrVector method. Is there nothing for raw iterators?
I believe this is the shortest you can get:
fn main() {
let v = vec!["a".to_string(), "b".to_string(), "c".to_string()];
let mut r = v.iter()
.rev()
.fold(String::new(), |r, c| r + c.as_str() + ".");
r.pop();
println!("{}", r);
}
The addition operation on String takes its left operand by value and pushes the second operand in-place, which is very nice - it does not cause any reallocations. You don't even need to clone() the contained strings.
I think, however, that the lack of concat()/connect() methods on iterators is a serious drawback. It bit me a lot too.
I don't know if they've heard our Stack Overflow prayers or what, but the itertools crate happens to have just the method you need - join.
With it, your example might be laid out as follows:
use itertools::Itertools;
let v = ["a", "b", "c"];
let connected = v.iter().rev().join(".");
Here's an iterator extension trait that I whipped up, just for you!
pub trait InterleaveExt: Iterator + Sized {
fn interleave(self, value: Self::Item) -> Interleave<Self> {
Interleave {
iter: self.peekable(),
value: value,
me_next: false,
}
}
}
impl<I: Iterator> InterleaveExt for I {}
pub struct Interleave<I>
where
I: Iterator,
{
iter: std::iter::Peekable<I>,
value: I::Item,
me_next: bool,
}
impl<I> Iterator for Interleave<I>
where
I: Iterator,
I::Item: Clone,
{
type Item = I::Item;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
// Don't return a value if there's no next item
if let None = self.iter.peek() {
return None;
}
let next = if self.me_next {
Some(self.value.clone())
} else {
self.iter.next()
};
self.me_next = !self.me_next;
next
}
}
It can be called like so:
fn main() {
let a = &["a", "b", "c"];
let s: String = a.iter().cloned().rev().interleave(".").collect();
println!("{}", s);
let v = vec!["a".to_string(), "b".to_string(), "c".to_string()];
let s: String = v.iter().map(|s| s.as_str()).rev().interleave(".").collect();
println!("{}", s);
}
I've since learned that this iterator adapter already exists in itertools under the name intersperse — go use that instead!.
Cheating answer
You never said you needed the original vector after this, so we can reverse it in place and just use join...
let mut v = vec!["a".to_string(), "b".to_string(), "c".to_string()];
v.reverse();
println!("{}", v.join("."))