Chunking a vector of strings - rust

I'm trying to chunk an vector of uneven length strings into a vector of even length strings. The laziest way I could think of doing this is to join the arguments into a string, convert the chars to a vector, and then use Vec::chunks. Unfortunately, I'm running into issues trying to collect the chunks into strings.
let args: Vec<String> = ["123", "4", "56"].iter().map(|&s| s.into()).collect();
let result: Vec<String> = args
.join(" ")
.chars()
.collect::<Vec<_>>()
.chunks(2)
.map(|c| c.collect::<String>())
.collect::<Vec<String>>();
assert_eq!(["12", "34", "56"], result);
Results in the error:
error[E0599]: no method named `collect` found for type `&[char]` in the current scope
--> src/main.rs:9:20
|
9 | .map(|c| c.collect::<String>())
| ^^^^^^^
|
= note: the method `collect` exists but the following trait bounds were not satisfied:
`&mut &[char] : std::iter::Iterator`
`&mut [char] : std::iter::Iterator`

You weren't far off:
let result: Vec<String> = args
.join("")
.chars()
.collect::<Vec<_>>()
.chunks(2)
.map(|x| x.iter().cloned().collect())
.collect();
println!("{:?}", result);
You probably don't want a space when joining them together.
You need to convert each chunk (which is a &[char]) into an iterator via .iter(). You then have to convert the iterated type from a &char to a char via .cloned().
I might write this using Itertools::chunks though:
use itertools::Itertools; // 0.8.0
fn main() {
let args = ["123", "4", "56"];
let together = args.iter().flat_map(|x| x.chars());
let result: Vec<String> = together
.chunks(2)
.into_iter()
.map(|x| x.collect())
.collect();
println!("{:?}", result);
}
flat_map avoids the need to create a String, it just chains one iterator to the next.
Itertools::chunks allows the programmer to not create an intermediate Vec. Instead, it has an internal vector that, IIRC, will only store up to n values in it before yielding a value. This way you are buffering a smaller amount of items.

Related

Concatenate a slice of `&str`s into an owned `String`

I am trying to concatenate every element in a slice of &strs (&[&str]) into a single owned String. For example, I want to turn &['Hello', ' world!'] into "Hello world!".
I tried to do this by converting the slice into an iterator, then mapping over the iterator and converting each &str into an owned String, then collect them all into a Vec<String> and running .join(""), but I keep getting a type error.
Here is my code:
fn concat_str(a: &[&str]) -> String {
a.into_iter().map(|s| s.to_owned()).collect::<Vec<String>>().join("")
}
fn main() {
let arr = ["Dog", "Cat", "Bird", "Lion"];
println!("{}", concat_str(&arr[..3])); // should print "DogCatBird"
println!("{}", concat_str(&arr[2..])); // should print "BirdLion"
println!("{}", concat_str(&arr[1..3])); // should print "CatBird"
}
And here is the compiler error that I am getting:
error[E0277]: a value of type `Vec<String>` cannot be built from an iterator over elements of type `&str`
--> code.rs:2:38
|
2 | a.into_iter().map(|s| s.to_owned()).collect::<Vec<String>>().join("")
| ^^^^^^^ value of type `Vec<String>` cannot be built from `std::iter::Iterator<Item=&str>`
|
= help: the trait `FromIterator<&str>` is not implemented for `Vec<String>`
note: required by a bound in `collect`
--> /Users/michaelfm1211/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/core/src/iter/traits/iterator.rs:1780:19
|
1780 | fn collect<B: FromIterator<Self::Item>>(self) -> B
| ^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `collect`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0277`.
It says that I cannot collect into a Vec<String> because the iterator is not of a String, but I thought that I had converted it to a String with .map(|s| s.to_owned()).
How can I fix this? I am also new to rust, so it would be very helpful is someone could explain what I did wrong.
into_iter will yield an iterator with Item=&&str. In your map, .to_owned() converts that into a &str, which doesn't work. There are a few ways to fix that, you could use .copied or .cloned to get a &str:
a.into_iter().copied().map(|s| s.to_owned()).collect::<Vec<String>>().join("")
// or
a.into_iter().cloned().map(|s| s.to_owned()).collect::<Vec<String>>().join("")
Or you could use .to_string() to get a String directly:
a.into_iter().map(|s| s.to_string()).collect::<Vec<String>>().join("")
Note, you can also just collect into a String when you don't want a separator directly:
a.into_iter().map(|s| s.to_string()).collect::<String>()
There are the methods join and concat on slice directly, so you can write:
fn concat_str(a: &[&str]) -> String {
a.concat()
}
which gives you the desired output
fn concat_str(a: &[&str]) -> String {
a.iter()
.cloned()
.collect()
}

What's the purpose of ".map(|&x| x)" in Rust

I'm learning Rust and noticed the following iterator pattern in a number of places:
let some_vector: &[& str] = &["hello", "world", "zombies", "pants"];
let result: Vec<&str> = some_vector
.iter()
.filter(|&x| *x == "hello")
.map(|&x| x)
.collect();
What's the purpose of that .map(|&x| x)? Why is it necessary? Does it create a copy?
When I remove it, I get the following compiler error:
error[E0277]: a value of type `Vec<&str>` cannot be built from an iterator over elements of type `&&str`
--> src/main.rs:7:6
|
7 | .collect();
| ^^^^^^^ value of type `Vec<&str>` cannot be built from `std::iter::Iterator<Item=&&str>`
|
= help: the trait `FromIterator<&&str>` is not implemented for `Vec<&str>`
note: required by a bound in `collect`
For more information about this error, try `rustc --explain E0277`.
So the map turns an iterator over references to string slices into an iterator over string slices? Removing one level of indirection? Is that right?
In addition to #AlexW's answer, actually there is no need to write that, because there is a builtin iterator adapter that does it better (more clear, more performant): copied().
let some_vector: &[&str] = &["hello", "world", "zombies", "pants"];
let result: Vec<&str> = some_vector
.iter()
.filter(|&x| *x == "hello")
.copied()
.collect();
There is also cloned() which is equal to .map(|x| x.clone()).
Assuming you're using 2021 edition, it converts from impl Iterator< Item = &&str> to impl Iterator< Item = &str>:
let some_vector: &[& str] = &["hello", "world", "zombies", "pants"];
let result: Vec<&str> = some_vector // &[&str]
.iter() // Iter<&str>
.filter(|&x| *x == "hello") // Impl Iterator< Item = &&str>
.map(|&x| x) // Impl Iterator< Item = &str>
.collect();
And the reason it's necessary is because the FromIterator trait is already implemented for &str as it's a relatively more common use case and it's not implemented for &&str as the error message says:
the trait `FromIterator<&&str>` is not implemented for `Vec<&str>`

Split string by char, then rejoin with new char insertions in Rust

I'd like to split a string by a char, then for each of those split chunks, append a new string. Then I'd like to rejoin these chunks into a single string.
fn main(){
let my_string_start = String::From("Hello, Goodmorning");
let augmented_string = my_string_start split(",").flat_map(|f| format!("{} and",f)).collect();
assert_eq!(augmented_string, String::from("Hello, and Goodmorning"));
}
But this gives error:
error[E0277]: `String` is not an iterator
--> src/main.rs:3:53
|
3 | let augmented_string = &my_string_start.split(",").flat_map(|f| format!("{} and",f)).collect();
| ^^^^^^^^ `String` is not an iterator; try calling `.chars()` or `.bytes()`
|
= help: the trait `Iterator` is not implemented for `String`
= note: required because of the requirements on the impl of `IntoIterator` for `String`
error[E0599]: the method `collect` exists for struct `FlatMap<std::str::Split<'_, &str>, String, [closure#src/main.rs:3:62: 3:85]>`, but its trait bounds were not satisfied
--> src/main.rs:3:87
|
3 | let augmented_string = &my_string_start.split(",").flat_map(|f| format!("{} and",f)).collect();
| ^^^^^^^ method cannot be called on `FlatMap<std::str::Split<'_, &str>, String, [closure#src/main.rs:3:62: 3:85]>` due to unsatisfied trait bounds
|
= note: the following trait bounds were not satisfied:
`String: IntoIterator`
which is required by `FlatMap<std::str::Split<'_, &str>, String, [closure#src/main.rs:3:62: 3:85]>: Iterator`
`FlatMap<std::str::Split<'_, &str>, String, [closure#src/main.rs:3:62: 3:85]>: Iterator`
which is required by `&mut FlatMap<std::str::Split<'_, &str>, String, [closure#src/main.rs:3:62: 3:85]>: Iterator`
What's a valid way to do this?
Playground
Two points:
You forgot what flat_map actually does.
It runs over an iterator and for each element it's expected to output a separate Iterator, those iterators are chained together into a single Iterator.
In your case you wanted to first split the string (every elements of split iterator is some &str), you then need to simply apply map (because we want to take a single element - part of initial string, and output a single element - its extended version)
There's also one unpleasant obstacle - we'll also extend the last part with ", and", which is not quite what you want.
Finally we collect the iterator into a new String.
You worked with my_string_start by reference.
&my_string_start is of type &str and you can't collect it later. But split method automatically takes the String by reference, so no worries about accidentally moving your string out. You can safely remove this &, and the whole thing will be collected in the end into another String
fn main(){
let my_string_start = String::from("Hello, Goodmorning");
let augmented_string: String = my_string_start
.split(",")
.map(|f| format!("{}, and ", f))
.collect();
assert_eq!(augmented_string, String::from("Hello, and Goodmorning"));
}
EDIT
I tried to solve the issue of the trailing , and after the last element, and solving it with current capabilities of Rust is quite ugly and dirty. There's one unstable feature of Rust, that is available in Nightly version of the compiler, and it's just so good for your exact task, take a look.
intersperse takes an iterator, and inserts some element in between every element (not before and not after the iterator, only inside the iterator). With it you simply get what you wanted
#![feature(iter_intersperse)]
fn main(){
let my_string_start = String::from("Hello, Goodmorning");
let augmented_string: String = my_string_start
.split(",")
.intersperse(", and")
.collect();
assert_eq!(augmented_string, String::from("Hello, and Goodmorning"));
}

Finding most frequently occurring string in a structure in Rust?

I'm looking for the string which occurs most frequently in the second part of the tuple of Vec<(String, Vec<String>)>:
use itertools::Itertools; // 0.8.0
fn main() {
let edges: Vec<(String, Vec<String>)> = vec![];
let x = edges
.iter()
.flat_map(|x| &x.1)
.map(|x| &x[..])
.sorted()
.group_by(|x| x)
.max_by_key(|x| x.len());
}
Playground
This:
takes the iterator
flat-maps to the second part of the tuple
turns elements into a &str
sorts it (via itertools)
groups it by string (via itertools)
find the group with the highest count
This supposedly gives me the group with the most frequently occurring string, except it doesn't compile:
error[E0599]: no method named `max_by_key` found for type `itertools::groupbylazy::GroupBy<&&str, std::vec::IntoIter<&str>, [closure#src/lib.rs:9:19: 9:24]>` in the current scope
--> src/lib.rs:10:10
|
10 | .max_by_key(|x| x.len());
| ^^^^^^^^^^
|
= note: the method `max_by_key` exists but the following trait bounds were not satisfied:
`&mut itertools::groupbylazy::GroupBy<&&str, std::vec::IntoIter<&str>, [closure#src/lib.rs:9:19: 9:24]> : std::iter::Iterator`
I'm totally lost in these types.
You didn't read the documentation for a function you are using. This is not a good idea.
This type implements IntoIterator (it is not an iterator itself),
because the group iterators need to borrow from this value. It should
be stored in a local variable or temporary and iterated.
Personally, I'd just use a BTreeMap or HashMap:
let mut counts = BTreeMap::new();
for word in edges.iter().flat_map(|x| &x.1) {
*counts.entry(word).or_insert(0) += 1;
}
let max = counts.into_iter().max_by_key(|&(_, count)| count);
println!("{:?}", max);
If you really wanted to use the iterators, it could look something like this:
let groups = edges
.iter()
.flat_map(|x| &x.1)
.sorted()
.group_by(|&x| x);
let max = groups
.into_iter()
.map(|(key, group)| (key, group.count()))
.max_by_key(|&(_, count)| count);

Create Vec<String> from literal

I want to do
fn main() {
let options: Vec<String> = vec!["a", "b", "c"].map(|s| s.to_owned()).collect();
}
because this seems like the easiest way to get a vector of owned Strings, but I get hit with this error:
error: no method named `map` found for type `std::vec::Vec<&str>` in the current scope
...
note: the method `map` exists but the following trait bounds were not satisfied:
`std::vec::Vec<&str> : std::iter::Iterator`, `[&str] : std::iter::Iterator`
I don't see where the need for the [&str] : std::iter::Iterator bound comes from. if you ignore the part with split_whitespace I'm basically doing what answers on this question recommend.
How should I be generating this vector?
if you ignore the part with split_whitespace
Yes, except you cannot ignore this part. The docs for split_whitespace state (emphasis mine):
The iterator returned
split_whitespace returns an iterator over the pieces of the string that were separated by whitespace, and map is a method on Iterator.
A Vec is not an iterator. You can see that Vec does not implement it's own map method:
no method named map found for type std::vec::Vec<&str>
And the compiler tries to suggest what you might have meant, but weren't quite achieving:
note: the method map exists but the following trait bounds were not satisfied:
You can get an iterator from a Vec by calling Vec::iter or into_iter:
fn main() {
let options: Vec<String> = vec!["a", "b", "c"].into_iter().map(|s| s.to_owned()).collect();
}
However, there's no need to allocate two vectors here, an array and a vector is more efficient:
let options: Vec<_> = ["a", "b", "c"].iter().map(|s| s.to_string()).collect();
An iterator over a slice returns references (&T) to the elements in the slice. Since each element is already a &str, the type of s is a &&str. Calling to_owned on a reference to a reference simply clones the reference. You could also have said .map(|&s| s.to_owned()), which dereferences the value once, producing a &str. Calling to_owned on a &str allocates a String.
If I had to create vectors of Strings repeatedly, I would use a macro:
macro_rules! vec_of_strings {
// match a list of expressions separated by comma:
($($str:expr),*) => ({
// create a Vec with this list of expressions,
// calling String::from on each:
vec![$(String::from($str),)*] as Vec<String>
});
}
fn main() {
let v1 = vec_of_strings![];
let v2 = vec_of_strings!["hello", "world", "!"];
println!("{:?}", v1);
println!("{:?}", v2);
}
Output:
[]
["hello", "world", "!"]

Resources