如何使我的 Rust 函数更通用和高效?

Posted

技术标签:

【中文标题】如何使我的 Rust 函数更通用和高效?【英文标题】:How can I make my Rust function more generic & efficient? 【发布时间】:2016-09-24 03:41:33 【问题描述】:

我有一个可以工作的函数,但比我想要的更专业,并且效率低下,我想解决这个问题。

有效但有缺陷的功能:

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 没有为类型 &lt;U as core::str::FromStr&gt;::Err 实现特征

我认为,通过巧妙的生命周期表示法和 Trait 要求,至少可以修复其中两个,谁知道也许有办法三对三。

使用一些建议修复的示例代码:

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

【问题讨论】:

如果您能发布一个完整的最小示例,我可以将其粘贴到本地文件中以启动并运行:) 【参考方案1】:

不幸的是,在保持通用性和不存在分配的情况下,没有办法做你想做的事。原因是你需要有人拥有你的字符串数据,但是如果flat_map(str::split_whitespace) 是在一个拥有的字符串的迭代器上执行的,那么就没有人再保留这些拥有的字符串了,因为str::split_whitespace 只借用了它被调用的字符串.但是,如果您将所有权“推”到调用链上,您将无法完全通用并按值接受拥有的字符串。

因此,有两种解决方案:将整个迭代器预先收集到Vec&lt;String&gt;(或将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&lt;String&gt; bound 允许同时传递 &amp;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")

这里我们只需要从迭代器项中获取一个&amp;strs,而不是Strings,所以我使用了AsRef&lt;str&gt;。但是,每一行不能只转换为Strings的序列;出于与上述完全相同的原因,必须将此序列收集到一个向量中 - 否则将没有人保护 S 类型的原始值不被破坏。

但是,如果您愿意失去一些通用性,则 可以避免使用 .map(String::from).collect::&lt;Vec&lt;_&gt;&gt;()。这是我上面提到的第二种解决方案。我们可以接受引用的迭代器:

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&lt;String&gt;。但是,您将无法将 Vec&lt;String&gt; 传递给此函数;你可以通过vec.iter(),但是:

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

在所有这些示例中,我已将 Iterator 更改为 IntoIterator - 这几乎总是您想要使用的,而不仅仅是 Iterator。例如,它允许您将集合直接传递给此类函数。其次,我添加了U::Err: Debug 条件,这是Result::unwrap 工作所必需的。最后,要解决“String not coercing to &str`”的问题,您始终可以使用显式闭包和方法语法来为您执行此强制。

【讨论】:

【参考方案2】:

无需额外分配的解决方案

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()

【讨论】:

以上是关于如何使我的 Rust 函数更通用和高效?的主要内容,如果未能解决你的问题,请参考以下文章

如何使我的 SQL 查询更高效,以便我的结果处理不会花费很长时间

高效的 if 语句 / for 循环

如何使我的 spriteNode 纹理通用?

Rust编程语言入门

#yyds干货盘点#为什么要学习Rust?

Rust之旅 linux下rust环境搭建