Cam*_*rzt 8 generics performance idioms rust
我有一个有效的功能,但比我想要的更专业,并且效率低下,我想解决这个问题.
工作但有缺陷的功能:
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.")
}
Run Code Online (Sandbox Code Playgroud)
我不喜欢这种实现的原因是:
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.")
}
Run Code Online (Sandbox Code Playgroud)
我看到的问题是:
str::split_whitespace是a String并且不会强制转换为astrstr::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);
}
Run Code Online (Sandbox Code Playgroud)
不幸的是,在保持通用性和缺少分配时,没有办法做你想做的事.原因是你需要有人拥有你的字符串数据,但如果flat_map(str::split_whitespace)是在拥有字符串的迭代器上执行,那么就没有人可以保留这些拥有的字符串,因为str::split_whitespace只能借用它所调用的字符串.但是,如果您将所有权"推"到调用链上,则您将无法完全通用并按值接受所拥有的字符串.
因此,有两种解决方案:将整个迭代器预先收集到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")
}
Run Code Online (Sandbox Code Playgroud)
(在这里试试)
它与您的第一个基本相同,但更通用.另请注意,您不需要修剪字符串的部分split_whitespace()- 后者将确保字符串的部分在其侧面没有空格.Into<String>bound允许一个传递两者&str和String迭代器,在后一种情况下,不会执行额外的副本.
或者,您可以将每行分成独立的字符串:
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")
}
Run Code Online (Sandbox Code Playgroud)
这里我们只需&str要从迭代器项中获取s,而不是Strings,所以我使用了AsRef<str>.但是,每一行不仅必须转换为Strings 序列; 由于与上述完全相同的原因,必须将此序列收集到一个向量中 - 否则将无法保持类型的原始值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")
}
Run Code Online (Sandbox Code Playgroud)
(在这里试试)
粗略地说,现在S值由其他人拥有,并且它们的生命周期大于范围iter_to_min(),因此您既不需要将每个部分转换为String也不需要将整个拆分结果收集到a Vec<String>.但是,您将无法将a传递Vec<String>给此函数; 但是你可以通过vec.iter():
let v: Vec<String> = vec!["0".into(), "1".into()];
iter_to_min(v.iter())
Run Code Online (Sandbox Code Playgroud)
在所有这些例子我已经改变Iterator到IntoIterator-这是几乎总是要使用的,而不是正是Iterator.例如,它允许您直接将集合传递给此类函数.其次,我添加了U::Err: Debug条件,这是Result::unwrap工作所必需的.最后,要修复"String not coercing to&str`"的问题,你总是可以使用显式闭包和方法语法来为你做这种强制.