Свести итератор строк в Vec <char> (или даже FromIterator <char>)

Oct 06 2020

Как сказано в заголовке, у меня есть 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?

Ответы

4 NovaDenizen Oct 07 2020 at 00:11

Основная проблема заключается в том, что 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()
}
2 MichaelAnderson Oct 07 2020 at 13:18

Если вы можете жить с временным распределением, вы можете преобразовывать каждую строку в 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);
}