【问题标题】:Flatten Iterator of Strings to Vec<char> (or even FromIterator<char>)将字符串的迭代器展平为 Vec<char>(甚至 FromIterator<char>)
【发布时间】:2021-01-21 11:43:31
【问题描述】:

正如标题所说,我有一个Strings 中的Iterator 并想生成一个Vec&lt;char&gt;,只需将它们全部连接起来(同时将每个String 视为一系列Unicode 标量值,正如.chars() 所做的那样)。 (我不需要在Strings 或类似的东西之间插入字符串,尽管我知道使用itertools::intersperse 不会更难,如other answers 所述。)

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 的引用;但是 lambda 获得了 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,并且我正在平面映射&amp;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 的迭代器抽象的情况下高效地实现此功能吗?

【问题讨论】:

标签: rust


【解决方案1】:

主要问题是闭包中的s: String 参数在任何人都可以使用依赖它的Chars 对象之前被删除。

这里有两种处理方法。第一个比您的代码更详细,但使用相同的类型签名。第二个依赖于 &amp;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()
}

【讨论】:

  • 嗯,既然你写了手写循环,我意识到你可以很容易地将它概括为返回S: Default + Extend&lt;char&gt;,它由许多与实现FromIterator&lt;char&gt; 相同的东西实现。
  • 这很合理。
【解决方案2】:

如果您可以接受临时分配,则可以在列表中进行时将每个字符串转换为 Vec&lt;char&gt; 并将其提供给 flat_map。这也将让更一般的案例工作。 您可以稍微更一般一些(如Sven Marnach 所建议的那样),只返回一个不带收集的impl Iterator&lt;Item = char&gt;,并允许调用者收集(如果他们愿意)。

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

【讨论】:

  • 除了返回类型 S: FromIterator&lt;char&gt;,您还可以简单地返回 impl Iterator&lt;Item = char&gt; 并让调用者以他们选择的任何方式使用它。
猜你喜欢
  • 1970-01-01
  • 2018-03-27
  • 2013-05-13
  • 1970-01-01
  • 2021-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多