Split string by char, then rejoin with new char insertions in Rust - 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"));
}

Related

How do I use collect::<HashSet<_>>.intersection() without the values becoming borrowed?

I am looping loop over a Vec<&str>, each time reassigning a variable that holds the intersection of the last two checked. This is resulting in "expected char, found &char". I think this is happening because the loop is a new block scope, which means the values from the original HashSet are borrowed, and go into the new HashSet as borrowed. Unfortunately, the type checker doesn't like that. How do I create a new HashSet<char> instead of HashSet<&char>?
Here is my code:
use std::collections::HashSet;
fn find_item_in_common(sacks: Vec::<&str>) -> char {
let mut item: Option<char> = None;
let mut sacks_iter = sacks.iter();
let matching_chars = sacks_iter.next().unwrap().chars().collect::<HashSet<_>>();
loop {
let next_sack = sacks_iter.next();
if next_sack.is_none() { break; }
let next_sack_values: HashSet<_> = next_sack.unwrap().chars().collect();
matching_chars = matching_chars.intersection(&next_sack_values).collect::<HashSet<_>>();
}
matching_chars.drain().nth(0).unwrap()
}
and here are the errors that I'm seeing:
error[E0308]: mismatched types
--> src/bin/03.rs:13:26
|
6 | let matching_chars = sacks_iter.next().unwrap().chars().collect::<HashSet<_>>();
| ---------------------------------------------------------- expected due to this value
...
13 | matching_chars = matching_chars.intersection(&next_sack_values).collect::<HashSet<_>>();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `char`, found `&char`
|
= note: expected struct `HashSet<char>`
found struct `HashSet<&char>`
By the way, what is that first error trying to tell me? It seems like it is missing something before or after "expected" -- <missing thing?> expected <or missing thing?> due to this value?
I also tried changing matching_chars = matching_chars to matching_chars = matching_chars.cloned() and I get the following error. I understand what the error is saying, but I don't know how to resolve it.
error[E0599]: the method `cloned` exists for struct `HashSet<char>`, but its trait bounds were not satisfied
--> src/bin/03.rs:13:41
|
13 | matching_chars = matching_chars.cloned().intersection(&next_sack_values).collect::<HashSet<_>>();
| ^^^^^^ method cannot be called on `HashSet<char>` due to unsatisfied trait bounds
|
::: /Users/brandoncc/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/std/src/collections/hash/set.rs:112:1
|
112 | pub struct HashSet<T, S = RandomState> {
| -------------------------------------- doesn't satisfy `HashSet<char>: Iterator`
|
= note: the following trait bounds were not satisfied:
`HashSet<char>: Iterator`
which is required by `&mut HashSet<char>: Iterator`
Your attempt at using cloned() was almost right but you have to call it after you create the iterator:
matching_chars.intersection(&next_sack_values).cloned().collect::<HashSet<_>>()
or for Copy types you should use the more appropriate .copied() adapter:
matching_chars.intersection(&next_sack_values).copied().collect::<HashSet<_>>()
Looking at the signature of HashSet::intersection will make this clearer:
pub fn intersection<'a>(
&'a self,
other: &'a HashSet<T, S>
) -> Intersection<'a, T, S>
The type Intersection<'a, T, S> implements Iterator<Item=&'a T>. So when you collect this iterator, you get a HashSet<&char> as opposed to a HashSet<char>.
The solution is simply to use .cloned on the iterator before you use .collect, since char is Clone, like so:
matching_chars = matching_chars.intersection(&next_sack_values).cloned().collect()
By the way, what is that first error trying to tell me?
The error is telling you that it expects char because (due to) the original value for matching_chars has type HashSet<char>.
I also tried changing matching_chars = matching_chars to matching_chars = matching_chars.cloned() and I get the following error. I understand what the error is saying, but I don't know how to resolve it.
Do you, really?
str::chars is an Iterator<Item=char>, so when you collect() to a hashset you get a HashSet<char>.
The problem is that intersection borrows the hashset, and since the items the hashset contains may or may not be Clone, it also has to borrow the set items, it can't just copy or clone them (not without restricting its flexibility anyway).
So that's where you need to add the cloned call, on the HashSet::intersection in order to adapt it from an Iterator<Item=&char> to an Iterator<Item=char>.
Or you can just use the & operator, which takes two borrowed hashsets and returns an owned hashset (requiring that the items be Clone).
Alternatively use Iterator::filter or Iterator::findon one of the sets, checking if the othersHashSet::containsthe item being looked at. Fundamentally that's basically whatintersection` does, and you know there's just one item at the end.

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()
}

Using mutable or immutable reference in a string chars for-loop

I'm confused how a mutable reference works in a for-loop while an immutable reference does not. Is it an iterator?
I found that after loop, the reference chars refers to value "".
No reference
fn main() {
let str_value: String = "abc".to_string();
let chars = str_value.chars();
for char_value in chars {
println!("char: {}", char_value);
}
}
char: a
char: b
char: c
Immutable reference
fn main() {
let str_value: String = "abc".to_string();
let chars = str_value.chars();
for char_value in &chars {
println!("char: {}", char_value);
}
}
error[E0277]: `&std::str::Chars<'_>` is not an iterator
--> src/main.rs:5:23
|
5 | for char_value in &chars {
| -^^^^^
| |
| `&std::str::Chars<'_>` is not an iterator
| help: consider removing the leading `&`-reference
|
= help: the trait `std::iter::Iterator` is not implemented for `&std::str::Chars<'_>`
= note: `std::iter::Iterator` is implemented for `&mut std::str::Chars<'_>`, but not for `&std::str::Chars<'_>`
= note: required by `std::iter::IntoIterator::into_iter`
Mutable reference
fn main() {
let str_value: String = "abc".to_string();
let mut chars = str_value.chars();
for char_value in &mut chars {
println!("char: {}", char_value);
}
// why chars equal ""?
assert_eq!(chars.as_str(), "");
}
char: a
char: b
char: c
chars is of type Chars which implements ("is a") iterator whose elements are of type char.
In Rust, you can use for-loops on things that implement ("are a") iterator.
Thus, your first example is covered by this: Iterates over chars.
The second example does not work because &chars has type &Chars (a borrowed, immutable reference), which does not implement Iterator.
In the third example, however, you have &mut Chars, and this is an iterator:
impl<I: Iterator + ?Sized> Iterator for &mut I {
type Item = I::Item;
fn next(&mut self) -> Option<I::Item> { (**self).next() }
// some details omitted
}
The above says: For any type I that is an Iterator and that may not satisfy ?Sized (all this holds for the type Chars), the type &mut I is an Iterator, whose iterated values are the same as the original iterated types (type Item = I::Item), and that delegates next (and some other methods) to the original iterator.
Thus, the third example sees &mut Chars, knows that Chars is an Iterator, and infers that then &mut Chars is also an Iterator that can be used in a for-loop.
as_str -- according to its documentation (see the example there) gives you the remaining (i.e. not yet iterated) substring, so after the iteration, it should just say "" (i.e. empty), since there is nothing left to be iterated over.

The trait `FnMut<(char,)>` is not implemented for `String` when trying to split a string

I need to split a String (not &str) by another String:
use std::str::Split;
fn main() {
let x = "".to_string().split("".to_string());
}
Why do I get this error and how to avoid it if I already have to operate on strings?
error[E0277]: the trait bound `std::string::String: std::ops::FnMut<(char,)>` is not satisfied
--> src/main.rs:4:32
|
4 | let x = "".to_string().split("".to_string());
| ^^^^^ the trait `std::ops::FnMut<(char,)>` is not implemented for `std::string::String`
|
= note: required because of the requirements on the impl of `std::str::pattern::Pattern<'_>` for `std::string::String`
According to the #rust-beginners IRC channel, this might be an example of Deref failing in 1.20.0-nightly. How to split a string in Rust? doesn't address the problem of splitting by String, not &str.
All is in the documentation. You can provide one of:
A &str,
A char,
A closure,
Those three types implement the Pattern trait. You are giving a String to split instead of a &str.
Example:
fn main() {
let x = "".to_string();
let split = x.split("");
}
I talked about this with #rust-beginners IRC channel and heard the following:
15:12:15 achird | d33tah: split accepts a Pattern, where Pattern can be &str or char, but you're passing a String (no idea why deref is not working)
15:13:01 d33tah | achird: thanks! how can I convert string to str?
15:13:03 achird | i think a simple split(&delimiter2) should fix the problem
15:16:26 calops | why isn't deref working though?
15:21:33 #mbrubeck | calops, d33tah: Deref coercions only work if one exact "expected" type is known. For a generic type like <P: Pattern>, coercion doesn't kick in.
15:24:11 #mbrubeck | d33tah: The error should definitely be improved... It should complain that `String` doesn't impl `Pattern`, instead of jumping straight to `FnMut(char)`
So basically, the solution is to add & before the delimiter string, like this:
fn main() {
let s1 = "".to_string();
let s2 = "".to_string();
let x = s1.split(&s2);
}

Chunking a vector of strings

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.

Resources