如何使我的Rust功能更通用&有效?

时间:2016-05-26 08:38:53

标签: performance generics rust idioms

我有一个有效的功能,但比我想要的更专业,并且效率低下,我想解决这个问题。

有效但有缺陷的功能:

fn iter_to_min<T>(i:T) -> i64 where T:Iterator<Item=String>{
    i.collect::<Vec<String>>()
        .iter()
        .flat_map(|s|s.split_whitespace())
        .map(str::trim)
        .map(str::parse::<i64>)
        .map(Result::unwrap)
        .min()
        .expect("No min found.")
}

我不喜欢这种实现的原因是:

  • i64是硬编码的,我想将此函数重用于u64以及其他可能的返回类型
  • 它收集它的输入只是为了立即迭代它,这是低效的(没有理由的堆分配)
  • 传递给flat_map的闭包可能在所有情况下都不会被LLVM优化

最接近我理想的功能:

use std::str::FromStr;

fn iter_to_min<T,U>(i:T) -> U where T:Iterator<Item=String>,U: Ord+FromStr{
    i.flat_map(str::split_whitespace)
        .map(str::trim)
        .map(str::parse::<U>)
        .map(Result::unwrap)
        .min()
        .expect("No min found.")
}

我看到的问题是:

  • 传递给str::split_whitespace的参数为String,不会强制转换为str
  • 传递给str::split_whitespace的参数不知道活得足够长
  • Result::unwrap并未抱怨core::fmt::Debug
  • 类型未实施特征<U as core::str::FromStr>::Err

我认为,凭借聪明的终身记谱法和特质要求,其中至少有两个可以修复,而且谁知道可能有三种方法可以去三个。

使用一些建议的修补程序的示例代码:

use std::io::BufRead;
use std::str::FromStr;
use std::fmt::Debug;

fn iter_to_min<T,U>(i:T) -> U where T:Iterator<Item=String>,U: Ord+FromStr, U::Err: Debug{
    i.collect::<Vec<String>>()
        .iter()
        .flat_map(|s|s.split_whitespace())
        .map(str::trim)
        .map(str::parse::<U>)
        .map(Result::unwrap)
        .min()
        .expect("No min found.")
}

fn main() {
    let a: Vec<_> = std::env::args().skip(1).collect();
    let m:i64 = if a.is_empty() {
        let s = std::io::stdin();
        let m = iter_to_min(s.lock().lines().map(Result::unwrap));
        m
    }else{
        iter_to_min(a.into_iter())
    };
    println!("{}", m);
}

2 个答案:

答案 0 :(得分:8)

不幸的是,在保持通用性和缺少分配时,没有办法做你想做的事。原因是你需要有人拥有你的字符串数据,但如果flat_map(str::split_whitespace)是在拥有字符串的迭代器上执行的,那么就没有人可以保留这些拥有的字符串,因为str::split_whitespace只能借用字符串它被召唤。但是,如果你&#34;推&#34;对调用链的所有权,您将无法完全通用并按价值接受所拥有的字符串。

因此,有两种解决方案:将整个迭代器预收集到Vec<String>(或将split_whitespace()产生的项目分别转换为拥有的字符串,然后再将它们收集到Vec ),或者接受引用的迭代器。

以下是我可以提出的第一个解决方案中最通用的版本:

use std::str::FromStr;
use std::fmt::Debug;

fn iter_to_min<S, T, U>(i: T) -> U
    where S: Into<String>,
          T: IntoIterator<Item=S>,
          U: Ord + FromStr,
          U::Err: Debug
{
    i.into_iter()
        .map(Into::into)
        .collect::<Vec<_>>()
        .iter()
        .flat_map(|s| s.split_whitespace())
        .map(str::parse::<U>)
        .map(Result::unwrap)
        .min()
        .expect("No min found")
}

(试试here

它与您的第一个基本相同,但更通用。另请注意,您不需要在split_whitespace()之后修剪字符串的部分 - 后者将确保字符串的各个部分在其侧面没有空格。 Into<String> bound允许一个传递&strString迭代器,在后一种情况下,不会执行额外的副本。

或者,您可以分别将每一行拆分为拥有的字符串:

fn iter_to_min<S, T, U>(i: T) -> U
    where S: AsRef<str>,
          T: IntoIterator<Item=S>,
          U: Ord + FromStr,
          U::Err: Debug
{
    i.into_iter()
        .flat_map(|s| s.as_ref().split_whitespace().map(String::from).collect::<Vec<_>>())
        .map(|s| s.parse::<U>())
        .map(Result::unwrap)
        .min()
        .expect("No min found")
}

这里我们只需要从迭代器项中获取&str,而不是String s,所以我使用了AsRef<str>。但是,每行不仅必须转换为String s的序列;必须将此序列收集到一个向量中,原因与上述完全相同 - 否则将无法保持类型S的原始值不被破坏。

但是如果你愿意失去一些通用性, 可能会避免.map(String::from).collect::<Vec<_>>()。这是我上面提到的第二个解决方案。我们可以接受迭代器而不是引用:

fn iter_to_min<'a, S: ?Sized, T, U>(i: T) -> U
    where S: AsRef<str> + 'a,
          T: IntoIterator<Item=&'a S>,
          U: Ord + FromStr,
          U::Err: Debug
{
    i.into_iter()
        .map(AsRef::as_ref)
        .flat_map(str::split_whitespace)
        .map(|s| s.parse::<U>())
        .map(Result::unwrap)
        .min()
        .expect("No min found")
}

(试试here

粗略地说,现在S值由其他人拥有,其生命周期大于iter_to_min()的范围,因此您既不需要将每个部分转换为String也不需要将整个拆分结果收集到Vec<String>。但是,您无法将Vec<String>传递给此函数;但是,您将能够通过vec.iter()

let v: Vec<String> = vec!["0".into(), "1".into()];
iter_to_min(v.iter())

在所有这些示例中,我已将Iterator更改为IntoIterator - 这几乎总是您想要使用的而不仅仅是Iterator。例如,它允许您直接将集合传递给此类函数。其次,我添加了U::Err: Debug条件,这是Result::unwrap工作所必需的。最后,为了解决&#34;字符串没有强制到&amp; str`的​​问题,你总是可以使用显式闭包和方法语法来为你做这种强制。

答案 1 :(得分:3)

没有额外分配的解决方案

use std::str::FromStr;

fn iter_to_min<T, U>(i: T) -> Option<U>
    where T: Iterator<Item = String>,
          U: Ord + FromStr
{
    i.filter_map(|string| {
            string.split_whitespace()
                .map(str::trim)
                .map(str::parse::<U>)
                .filter_map(Result::ok)
                .min()
        })
        .min()
}