Свести итератор строк в Vec <char> (или даже FromIterator <char>)
Как сказано в заголовке, у меня есть Iteratorиз Strings, и я хотел бы создать a Vec<char>, просто объединив их все (и рассматривая каждое Stringкак последовательность скалярных значений Unicode, как это .chars()происходит). (Мне не нужно чередовать строку между Strings или чем-то в этом роде, хотя я понимаю, что это будет не намного сложнее, например itertools::intersperse, как описано в других ответах .)
pub fn flatten_strings(ss: impl Iterator<Item=String>) -> Vec<char> {
// ???
}
(Было бы неплохо, но не особенно необходимо, если бы я мог реализовать чуть более общую подпись типа :)
pub fn flatten_strings<S>(ss: impl Iterator<Item=String>) -> S where S: FromIterator<char> {
// ???
}
Во всяком случае, вот простая попытка:
pub fn flatten_strings(ss: impl Iterator<Item=String>) -> Vec<char> {
ss.flat_map(|s| s.chars()).collect()
}
К сожалению, это не работает, потому что charsвозвращает Chars, содержащую ссылку на объект, на котором Stringон основан; но лямбда стала владельцем объекта Stringи отбросит его, когда вернется.
error[E0515]: cannot return value referencing function parameter `s`
--> src/main.rs:2:21
|
2 | ss.flat_map(|s| s.chars()).collect()
| -^^^^^^^^
| |
| returns a value referencing data owned by the current function
| `s` is borrowed here
Я могу исправить это, просто собрав все Strings в промежуточный вектор, чтобы у меня был кто-то, владеющий всеми Strings, и я отображал итератор &Strings вместо Strings; но необходимость выделения этого промежуточного вектора Strings кажется неэффективной и несколько лишает смысла использование хороших абстракций итератора Rust:
pub fn flatten_strings(ss: impl Iterator<Item=String>) -> Vec<char> {
ss.collect::<Vec<String>>().iter().flat_map(|s| s.chars()).collect()
}
Точно так же я мог бы обработать цикл, но это тоже кажется некрасивым. Могу ли я эффективно реализовать эту функцию без дополнительных выделений, но также не покидая абстракций итератора Rust?
Ответы
Основная проблема заключается в том, что s: Stringпараметр в закрытии удаляется до того, как кто-либо сможет использовать Charsобъект, который от него зависит.
Вот два способа справиться с этим. Первый более подробный, чем ваш код, но использует ту же сигнатуру типа. Второй полагается на итератор, &strкоторый переживает функцию и все ее Charsобъекты.
pub fn flatten_strings(ss: impl Iterator<Item=String>) -> Vec<char> {
let mut res = Vec::new();
for s in ss {
res.extend(s.chars());
}
res
}
pub fn flatten_strings2<'a>(ss: impl Iterator<Item=&'a str>) -> Vec<char> {
ss.flat_map(|s| s.chars()).collect()
}
Если вы можете жить с временным распределением, вы можете преобразовывать каждую строку в a по Vec<char>мере продвижения по списку и передавать это в flat_map. Это позволит работать и более общему случаю. Вы можете пойти немного более обобщенно (как предлагает Свен Марнах ) и просто вернуть impl Iterator<Item = char>без сбора и разрешить вызывающему абоненту сбор, если он хочет.
use std::collections::HashSet;
use core::iter::FromIterator;
pub fn flatten_strings(ss: impl Iterator<Item=String>) -> Vec<char> {
ss.flat_map(|s| s.chars().collect::<Vec<_>>()).collect()
}
pub fn flatten_strings2<S>(ss: impl Iterator<Item=String>) -> S where S: FromIterator<char> {
ss.flat_map(|s| s.chars().collect::<Vec<_>>()).collect()
}
pub fn flatten_strings3(ss: impl Iterator<Item=String>) -> impl Iterator<Item = char> {
ss.flat_map(|s| s.chars().collect::<Vec<_>>())
}
fn main() {
let v = vec!["A string ".to_string(), "another".to_string() ];
println!("{:?}",flatten_strings(v.clone().into_iter()));
let h: HashSet<char> = flatten_strings2(v.clone().into_iter());
println!("{:?}",h);
let h: HashSet<char> = flatten_strings3(v.clone().into_iter()).collect();
println!("{:?}",h);
}