如何让我的 Rust 函数更通用和高效?
How can I make my Rust function more generic & efficient?
我有一个功能可以工作,但比我想要的更专业,而且效率低下,我想解决这个问题。
有效但有缺陷的功能:
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
和可能的其他 return 类型重用此函数
- 它收集它的输入只是为了立即迭代它,这是低效的(无缘无故的堆分配)
- 传递给
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
实现
我认为通过巧妙的生命周期符号和 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);
}
不幸的是,在保持通用性和没有分配的情况下,没有办法做你想做的事。原因是你需要有人拥有你的字符串数据,但是如果 flat_map(str::split_whitespace)
是在拥有的字符串的迭代器上执行的,那么就没有人再保留这些拥有的字符串,因为 str::split_whitespace
只是借用了字符串它被调用。但是,如果您 "push" 调用链上的所有权,您将无法完全通用并按值接受拥有的字符串。
因此,有两种解决方案:将整个迭代器预收集到一个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)
它与您的第一个基本相同,但更通用。另请注意,您不需要 trim split_whitespace()
之后的字符串部分 - 后者将确保字符串的各部分两侧没有空格。 Into<String>
绑定允许一个人同时传递 &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")
}
这里我们只需要从迭代器项中获取一个&str
s,而不是String
s,所以我使用了AsRef<str>
。但是,每一行不仅要转换为 String
的序列;出于与上述完全相同的原因,必须将此序列收集到一个向量中 - 否则就没有人可以防止 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
工作所必需的。最后,要解决“String not coercing to &str` 的问题,您始终可以使用显式闭包和方法语法来为您执行此强制转换。
没有额外分配的解决方案
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()
}
我有一个功能可以工作,但比我想要的更专业,而且效率低下,我想解决这个问题。
有效但有缺陷的功能:
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
和可能的其他 return 类型重用此函数- 它收集它的输入只是为了立即迭代它,这是低效的(无缘无故的堆分配)
- 传递给
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
实现
我认为通过巧妙的生命周期符号和 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);
}
不幸的是,在保持通用性和没有分配的情况下,没有办法做你想做的事。原因是你需要有人拥有你的字符串数据,但是如果 flat_map(str::split_whitespace)
是在拥有的字符串的迭代器上执行的,那么就没有人再保留这些拥有的字符串,因为 str::split_whitespace
只是借用了字符串它被调用。但是,如果您 "push" 调用链上的所有权,您将无法完全通用并按值接受拥有的字符串。
因此,有两种解决方案:将整个迭代器预收集到一个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)
它与您的第一个基本相同,但更通用。另请注意,您不需要 trim split_whitespace()
之后的字符串部分 - 后者将确保字符串的各部分两侧没有空格。 Into<String>
绑定允许一个人同时传递 &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")
}
这里我们只需要从迭代器项中获取一个&str
s,而不是String
s,所以我使用了AsRef<str>
。但是,每一行不仅要转换为 String
的序列;出于与上述完全相同的原因,必须将此序列收集到一个向量中 - 否则就没有人可以防止 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
工作所必需的。最后,要解决“String not coercing to &str` 的问题,您始终可以使用显式闭包和方法语法来为您执行此强制转换。
没有额外分配的解决方案
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()
}