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())
}
Related
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
I am trying to use a BTreeSet<(String, String, String)> as a way to create a simple in-memory 'triple store'.
To be precise:
type Entity = String;
type Attribute = String;
type Value = String;
type EAV = (Entity, Attribute, Value);
type EAVSet = BTreeSet<EAV>;
pub fn example_db() -> EAVSet {
let mut example: EAVSet = BTreeSet::new();
insert_strtup(&mut example, ("1", "type", "user"));
insert_strtup(&mut example, ("1", "user/name", "Arthur Dent"));
insert_strtup(&mut example, ("1", "user/age", "33"));
insert_strtup(&mut example, ("2", "type", "user"));
insert_strtup(&mut example, ("2", "user/name", "Ford Prefect"));
insert_strtup(&mut example, ("2", "user/age", "42"));
return example;
}
fn insert_strtup(db: &mut EAVSet, val: (&str, &str, &str)) -> () {
db.insert((val.0.to_string(), val.1.to_string(), val.2.to_string()));
}
pub fn example() {
let db = example_db();
// How to customize this?
let range: (Bound<EAV>, Bound<EAV>) = (Bound::Unbounded, Bound::Unbounded);
for elem in eavt.range(range) {
println!("{:?}", elem);
}
}
The problem I am facing, is that I want people to be able to iterate over a subrange of values in the set. However, a simple usage of std::ops::Bound is not possible because we store a tuples with multiple fields.
I'd like to be able to build range-queries for all of the following:
all entities;
all entities with an ID in range x..y;
all fields of entity 1;
the current value of entity 1's "user/age" field).
The only idea which came to mind so far, is to use a string key which we know for a fact compares lower resp. higher than what we're looking for for the 'placeholder' fields. But this feels very hackish/error-prone and like reinventing the wheel.
Is there maybe a way to turn a (Bound<String>, Bound<String>, Bound<String>) into a Bound<(String, String, String)> maybe?
Or is there another approach to take here?
EDIT: Filtering/querying a multi-key btree index in Rust shows one solution by wrapping all values in an ordered Enum (Min, Exact(String), Max), but this solution requires altering what kind of values are stored inside the BTreeSet. It also feels like adding a memory overhead as we're in actuality never storing anything other than Exact(some_string) inside. Is there another approach that does not require altering the type of values stored in the BTreeSet?
Since Borrow always returns a reference (grrrrrrrr), and Borrowed isn't necessarily Copy, you might be able to rely on a sentinel memory address?
Note that since associated static items aren't allowed, you probably need a copy of this code for each type you want to use.
use std::borrow::Borrow;
use std::cmp::Ordering;
#[repr(transparent)]
pub struct StringWithMinMaxSentinel(String);
// must be static, not const, to ensure a constant memory address
pub static STRING_MIN_SENTINEL: StringWithMinMaxSentinel = StringWithMinMaxSentinel(String::new());
pub static STRING_MAX_SENTINEL: StringWithMinMaxSentinel = StringWithMinMaxSentinel(String::new());
impl Borrow<StringWithMinMaxSentinel> for String {
fn borrow(self: &String) -> &StringWithMinMaxSentinel {
unsafe { &*(self as *const String as *const StringWithMinMaxSentinel) }
}
}
impl PartialEq for StringWithMinMaxSentinel {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(self, other) || (!std::ptr::eq(self, &STRING_MIN_SENTINEL) && !std::ptr::eq(other, &STRING_MAX_SENTINEL) && !std::ptr::eq(other, &STRING_MIN_SENTINEL) && !std::ptr::eq(self, &STRING_MAX_SENTINEL) && self.0.eq(&other.0))
}
}
impl Eq for StringWithMinMaxSentinel {}
impl PartialOrd for StringWithMinMaxSentinel {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for StringWithMinMaxSentinel {
fn cmp(&self, other: &Self) -> Ordering {
if std::ptr::eq(self, other) {
Ordering::Equal
} else if std::ptr::eq(self, &STRING_MIN_SENTINEL) || std::ptr::eq(other, &STRING_MAX_SENTINEL) {
Ordering::Less
} else if std::ptr::eq(self, &STRING_MAX_SENTINEL) || std::ptr::eq(other, &STRING_MIN_SENTINEL) {
Ordering::Greater
} else {
self.0.cmp(&other.0)
}
}
}
I'd like to be able to build range-queries for all of the following:
all entities;
all entities with an ID in range x..y;
all fields of entity 1;
the current value of entity 1's "user/age" field).
Is there an[other] approach that does not require altering the type of
values stored in the BTreeSet?
Given the above constraints, the following works. Since everything is a string, ranges use string comparison, meaning "a".."b" represents all strings starting with "a". The empty string is a natural minimal string value, but there is no ready maximal string value, so for that we use a large static string. This is of course not nice at all. This could perhaps be improved by using Option instead of String, with the None value standing for the maximum instead, Some("") would be the minimum. You would then also have to implement your own comparison...
use std::collections::BTreeSet;
use std::ops::Bound;
type Entity = String;
type Attribute = String;
type Value = String;
type EAV = (Entity, Attribute, Value);
type EAVSet = BTreeSet<EAV>;
pub fn example_db() -> EAVSet {
let mut example: EAVSet = BTreeSet::new();
insert_strtup(&mut example, ("1", "type", "user"));
insert_strtup(&mut example, ("1", "user/name", "Arthur Dent"));
insert_strtup(&mut example, ("1", "user/age", "33"));
insert_strtup(&mut example, ("2", "type", "user"));
insert_strtup(&mut example, ("2", "user/name", "Ford Prefect"));
insert_strtup(&mut example, ("2", "user/age", "42"));
insert_strtup(&mut example, ("11", "type", "user"));
insert_strtup(&mut example, ("11", "user/name", "Arthur Dent"));
insert_strtup(&mut example, ("11", "user/age", "33"));
insert_strtup(&mut example, ("12", "type", "user"));
insert_strtup(&mut example, ("12", "user/name", "Ford Prefect"));
insert_strtup(&mut example, ("12", "user/age", "42"));
return example;
}
fn insert_strtup(db: &mut EAVSet, val: (&str, &str, &str)) -> () {
db.insert((val.0.to_string(), val.1.to_string(), val.2.to_string()));
}
static MAX_STRING: &str = "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ";
pub fn main() {
let db = example_db();
// How to customize this?
let range: (Bound<EAV>, Bound<EAV>) = (Bound::Unbounded, Bound::Unbounded);
for elem in db.range(range) {
println!("{:?}", elem);
}
println!("\tall entities with an ID in range \"11\"..=\"12\":");
let range = (
Bound::Included(("11".to_string(), "".to_string(), "".to_string())),
Bound::Excluded(("120".to_string(), "".to_string(), "".to_string())),
);
for elem in db.range(range) {
println!("{:?}", elem);
}
println!("\tall fields of entity 1:");
let range = (
Bound::Included(("1".to_string(), "".to_string(), "".to_string())),
Bound::Excluded(("10".to_string(), "".to_string(), "".to_string())),
);
for elem in db.range(range) {
println!("{:?}", elem);
}
println!("\tthe current value of entity 1's \"user/age\" field:");
let range = (
Bound::Included(("1".to_string(), "user/age".to_string(), "".to_string())),
Bound::Excluded(("1".to_string(), "user/age".to_string(), MAX_STRING.to_string())),
);
for elem in db.range(range) {
println!("{:?}", elem);
}
}
I am receiving data in the form of a string vector, and need to populate a struct using a subset of the values, like this:
const json: &str = r#"["a", "b", "c", "d", "e", "f", "g"]"#;
struct A {
third: String,
first: String,
fifth: String,
}
fn main() {
let data: Vec<String> = serde_json::from_str(json).unwrap();
let a = A {
third: data[2],
first: data[0],
fifth: data[4],
};
}
This doesn't work because I'm moving values out of the vector. The compiler believes that this leaves data in an uninitialized state that can cause problems, but because I never use data again, it shouldn't matter.
The conventional solution is swap_remove, but it is problematic because the elements are not accessed in reverse order (assuming the structure is populated top to bottom).
I solve this now by doing a mem::replace and having data as mut, which clutters this otherwise clean code:
fn main() {
let mut data: Vec<String> = serde_json::from_str(json).unwrap();
let a = A {
third: std::mem::replace(&mut data[2], "".to_string()),
first: std::mem::replace(&mut data[0], "".to_string()),
fifth: std::mem::replace(&mut data[4], "".to_string())
};
}
Is there an alternative to this solution that doesn't require me to have all these replace calls and data unnecessarily mut?
I've been in this situation, and the cleanest solution I've found was to create an extension:
trait Extract: Default {
/// Replace self with default and returns the initial value.
fn extract(&mut self) -> Self;
}
impl<T: Default> Extract for T {
fn extract(&mut self) -> Self {
std::mem::replace(self, T::default())
}
}
And in your solution, you can replace the std::mem::replace with it:
const JSON: &str = r#"["a", "b", "c", "d", "e", "f", "g"]"#;
struct A {
third: String,
first: String,
fifth: String,
}
fn main() {
let mut data: Vec<String> = serde_json::from_str(JSON).unwrap();
let _a = A {
third: data[2].extract(),
first: data[0].extract(),
fifth: data[4].extract(),
};
}
That's basically the same code, but it is much more readable.
If you like funny things, you can even write a macro:
macro_rules! vec_destruc {
{ $v:expr => $( $n:ident : $i:expr; )+ } => {
let ( $( $n ),+ ) = {
let mut v = $v;
(
$( std::mem::replace(&mut v[$i], Default::default()) ),+
)
};
}
}
const JSON: &str = r#"["a", "b", "c", "d", "e", "f", "g"]"#;
#[derive(Debug)]
struct A {
third: String,
first: String,
fifth: String,
}
fn main() {
let data: Vec<String> = serde_json::from_str(JSON).unwrap();
vec_destruc! { data =>
first: 0;
third: 2;
fifth: 4;
};
let a = A { first, third, fifth };
println!("{:?}", a);
}
In small cases like this (also seen in naïve command line argument processing), I transfer ownership of the vector into an iterator and pop all the values off, keeping those I'm interested in:
fn main() {
let data: Vec<String> = serde_json::from_str(json).unwrap();
let mut data = data.into_iter().fuse();
let first = data.next().expect("Needed five elements, missing the first");
let _ = data.next();
let third = data.next().expect("Needed five elements, missing the third");
let _ = data.next();
let fifth = data.next().expect("Needed five elements, missing the fifth");
let a = A {
third,
first,
fifth,
};
}
I'd challenge the requirement to have a vector, however. Using a tuple is simpler and avoids much of the error handling needed, if you have exactly 5 elements:
fn main() {
let data: (String, String, String, String, String) = serde_json::from_str(json).unwrap();
let a = A {
third: data.2,
first: data.0,
fifth: data.4,
};
}
See also:
How can I ignore extra tuple items when deserializing with Serde? ("trailing characters" error)
Another option is to use a vector of Option<String>. This allows us to move the values out, while keeping track of what values have been moved, so they are not dropped with the vector.
let mut data: Vec<Option<String>> = serde_json::from_str(json).unwrap();
let a = A {
third: data[2].take().unwrap(),
first: data[0].take().unwrap(),
fifth: data[4].take().unwrap(),
};
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 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("."))