Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flatten Iterator of Strings to Vec<char> (or even FromIterator<char>)

Tags:

rust

As the title says, I have an Iterator of Strings and would like to produce a Vec<char>, just by concatenating all of them (and while considering each String as a sequence of Unicode Scalar Values, as .chars() does). (I don't need to interleave a string between the Strings or anything like that, although I understand it wouldn't be much harder with, e.g. itertools::intersperse, as covered in other answers.)

pub fn flatten_strings(ss: impl Iterator<Item=String>) -> Vec<char> {
    // ???
}

(It would be nice, but not particularly necessary, if I could implement the slightly more general type signature:)

pub fn flatten_strings<S>(ss: impl Iterator<Item=String>) -> S where S: FromIterator<char> {
    // ???
}

Anyway, here's a simple attempt:

pub fn flatten_strings(ss: impl Iterator<Item=String>) -> Vec<char> {
    ss.flat_map(|s| s.chars()).collect()
}

Unfortunately, this doesn't work, because chars returns Chars, which contains a reference to the String it's based on; but the lambda took ownership of the String and will drop it when it returns.

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

I can fix this by just collecting all the Strings into an intermediate vector, so that I have somebody owning all the Strings, and am flatmapping an iterator of &Strings instead of Strings; but having to allocate that intermediate vector of Strings seems inefficient and somewhat defeat the point of using Rust's nice iterator abstractions:

pub fn flatten_strings(ss: impl Iterator<Item=String>) -> Vec<char> {
    ss.collect::<Vec<String>>().iter().flat_map(|s| s.chars()).collect()
}

Similarly, I could handroll a loop, but that also seems ugly. Can I implement this function efficiently without additional allocations, but also without leaving Rust's iterator abstractions?

like image 300
betaveros Avatar asked Feb 02 '26 02:02

betaveros


2 Answers

The main problem is that the s: String parameter in the closure is dropped before anyone can use the Chars object that depends on it.

Here are two ways to deal with that. The first is more verbose than your code, but uses the same type signature. The second relies on an iterator of &str that outlive the function and all its Chars objects.

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()
}
like image 199
NovaDenizen Avatar answered Feb 04 '26 14:02

NovaDenizen


If you can live with a temporary allocation, you can convert each string to a Vec<char> as you progress through the list and provide that to the flat_map. This will let the more general case work too. You can go slightly more general (as suggested by Sven Marnach) and just return an impl Iterator<Item = char> without the collect, and allow the caller collect if they want.

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);
}
like image 22
Michael Anderson Avatar answered Feb 04 '26 16:02

Michael Anderson



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!