我决定尝试在Rust中使用毯子实现一个trait,并且要实现的测试方法是一个trait,该trait通过迭代器与元素一起返回argmax。现在的实现是
use num::Bounded;
trait Argmax<T> {
fn argmax(self) -> (usize, T);
}
impl<I, T> Argmax<T> for I
where
I: Iterator<Item = T>,
T: std::cmp::PartialOrd + Bounded,
{
fn argmax(self) -> (usize, T) {
self.enumerate()
.fold((0, T::min_value()), |(i_max, val_max), (i, val)| {
if val >= val_max {
(i, val)
} else {
(i_max, val_max)
}
})
}
}
用下面的代码测试
fn main() {
let v = vec![1., 2., 3., 4., 2., 3.];
println!("v: {:?}", v);
let (i_max, v_max) = v.iter().copied().argmax();
println!("i_max: {}nv_max: {}", i_max, v_max);
}
工作,而
fn main() {
let v = vec![1., 2., 3., 4., 2., 3.];
println!("v: {:?}", v);
let (i_max, v_max) = v.iter().argmax();
println!("i_max: {}nv_max: {}", i_max, v_max);
}
不能编译,并给出以下错误:
--> src/main.rs:27:35
|
27 | let (i_max, v_max) = v.iter().argmax();
| ^^^^^^ method cannot be called on `std::slice::Iter<'_, {float}>` due to unsatisfied trait bounds
|
= note: the following trait bounds were not satisfied:
`<&std::slice::Iter<'_, {float}> as Iterator>::Item = _`
which is required by `&std::slice::Iter<'_, {float}>: Argmax<_>`
`&std::slice::Iter<'_, {float}>: Iterator`
which is required by `&std::slice::Iter<'_, {float}>: Argmax<_>`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0599`.
我认为问题源于.iter()
在引用上循环,而.iter().copied()
在实际值上循环,但我仍然无法理解错误消息以及如何使其泛型并使用循环引用。
编辑:在被建议尝试使用关联类型而不是泛型类型实现上述功能之后,最终得到了这个工作实现,供以后参考:
trait Argmax {
type Maximum;
fn argmax(self) -> Option<(usize, Self::Maximum)>;
}
impl<I> Argmax for I
where
I: Iterator,
I::Item: std::cmp::PartialOrd,
{
type Maximum = I::Item;
fn argmax(mut self) -> Option<(usize, Self::Maximum)> {
let v0 = match self.next() {
Some(v) => v,
None => return None,
};
Some(
self.enumerate()
.fold((0, v0), |(i_max, val_max), (i, val)| {
if val > val_max {
(i + 1, val) // Add 1 as index is one off due to next() above
} else {
(i_max, val_max)
}
}),
)
}
}
这个实现也将Bounded
作为依赖项移除,取而代之的是检查迭代器是否为空,如果不是,则使用迭代器返回的第一个元素初始化当前最大值。这个实现返回它找到的第一个最大值的索引。
我还是不能理解错误信息
不幸的是,错误消息是模糊的,因为它没有告诉您<&std::slice::Iter<'_, {float}> as Iterator>::Item
是什么,这是关键的事实-它不是什么。(可能没有帮助,{float}
,一个尚未选择的数字类型,涉及。我也不确定&
在那里做什么,因为没有涉及到迭代器的引用。
但是,如果您查找std::slice::Iter<'a, T>
的文档,您会发现它的项目类型是&'a T
,所以在本例中是&'a {float}
。
这告诉你你已经知道的:迭代器是基于引用的。不幸的是,错误消息并没有告诉您有关问题其余部分的更多信息。但是,如果我检查num::Bounded
的文档,我发现,不出所料,Bounded
没有实现对数字的引用。这并不奇怪,因为引用必须指向存在于内存中的值,因此构造不借用某些现有数据结构的引用可能会很棘手或不可能。(我认为在这种情况下可能是可能的,但num
没有实现)
以及如何使其泛型并使用循环遍历引用
这是不可能的,只要你选择使用Bounded
特性,因为Bounded
不是为引用原始数而实现的,并且不可能为&T
和T
提供两个不同的毯子实现。
(您可以为自己的类型MyWrapper<f32>
实现Bounded
,并引用它,但随后用户必须处理该包装器。)
以下是一些选项:
-
保留你目前拥有的代码,并接受编写
.copied()
的需要。在其他迭代器中出现这种情况并不罕见——不要为了避免一个额外的函数调用而使代码变得更加复杂。 -
编写一个返回类型为
Option<(usize, T)>
的argmax()
版本,当迭代器为空时生成None
。然后,不需要使用Bounded
,代码将仅与PartialEq
约束一起工作。此外,当迭代器为空时,它不会返回无意义的索引和值——这在Rust代码中通常被认为是一种优点。如果(0, T::min_value())
是适合其应用程序的答案,则调用者总是可以使用.unwrap_or_else()
。 -
编写一个版本的
argmax()
,它使用一个单独的初始值,而不是使用T::min_value()
。
PartialOrd
是为引用实现的;这里的问题是num::Bounded
不是。如果您使argmax
函数遵循在迭代器为空时返回None
的约定(如Iterator::max
所做的那样),则可以完全摆脱对num::Bounded
特性的依赖:
fn argmax(self) -> Option<(usize, T)> {
let mut enumerated = self.enumerate();
enumerated.next().map(move |first| {
enumerated.fold(first, |(i_max, val_max), (i, val)| {
if val >= val_max {
(i, val)
} else {
(i_max, val_max)
}
})
})
}
这也允许在自定义(潜在的)非数字可比较类型的迭代器上实现Argmax
。
作为题外话,您可能希望将Argmax
trait转换为使用关联类型而不是泛型类型,就像Iterator
一样。
游乐场