正确地为迭代器实现argmax trait



我决定尝试在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不是为引用原始数而实现的,并且不可能为&TT提供两个不同的毯子实现。

(您可以为自己的类型MyWrapper<f32>实现Bounded,并引用它,但随后用户必须处理该包装器。)

以下是一些选项:

  1. 保留你目前拥有的代码,并接受编写.copied()的需要。在其他迭代器中出现这种情况并不罕见——不要为了避免一个额外的函数调用而使代码变得更加复杂。

  2. 编写一个返回类型为Option<(usize, T)>argmax()版本,当迭代器为空时生成None。然后,不需要使用Bounded,代码将仅与PartialEq约束一起工作。此外,当迭代器为空时,它不会返回无意义的索引和值——这在Rust代码中通常被认为是一种优点。如果(0, T::min_value())是适合其应用程序的答案,则调用者总是可以使用.unwrap_or_else()

  3. 编写一个版本的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

作为题外话,您可能希望将Argmaxtrait转换为使用关联类型而不是泛型类型,就像Iterator一样。

游乐场

相关内容

  • 没有找到相关文章