【问题标题】:Correctly make blanket implementation of argmax trait for iterators正确地为迭代器全面实现 argmax 特征
【发布时间】:2021-11-15 07:28:43
【问题描述】:

我决定尝试使用一揽子实现在 Rust 中创建一个 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 作为依赖项,而是检查迭代器是否为空,如果不是,则使用迭代器返回的第一个元素初始化当前最大值。此实现返回它找到的第一个最大值的索引。

【问题讨论】:

    标签: generics rust iterator


    【解决方案1】:

    我仍然无法理解错误消息

    不幸的是,错误消息很神秘,因为它没有告诉您&lt;&amp;std::slice::Iter&lt;'_, {float}&gt; as Iterator&gt;::Item 是什么,这是关键事实——它不是什么。 (可能涉及到{float},一种尚未选择的数字类型并没有帮助。我也不确定&amp; 在那里做什么,因为没有提到涉及的迭代器。)

    但是,如果你查看the documentation for std::slice::Iter&lt;'a, T&gt;,你会发现它的项目类型是&amp;'a T,所以在这种情况下,&amp;'a {float}

    这告诉你你已经知道的:迭代器是过度引用。不幸的是,错误消息并没有告诉您有关问题的其余部分的太多信息。但是,如果我查看num::Bounded 的文档,我会毫不奇怪地发现Bounded 没有实现对数字的引用。这并不奇怪,因为引用必须指向内存中存在的值,因此构造不借用某些现有数据结构的引用可能很棘手或不可能。 (我认为在这种情况下是可能的,但num 还没有实现。)

    以及如何使其通用并使用循环引用。

    只要你选择使用Bounded trait 是不可能的,因为Bounded 没有实现对原始数字的引用,并且不可能为&amp;TT 提供两种不同的一揽子实现.

    (您可以为自己的类型MyWrapper&lt;f32&gt; 实现Bounded,并对其进行引用,但随后用户必须处理该包装器。)

    这里有一些选项:

    1. 保留您当前拥有的代码,并满足编写.copied() 的需要。在其他迭代器中出现这种情况并不少见——不要仅仅为了避免一个额外的函数调用而使代码更加繁琐。

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

    3. 编写一个带有单独初始值的argmax() 版本,而不是使用T::min_value()

    【讨论】:

    • 太好了,感谢您的详细和友好的回答!既然你这么说,解决方案似乎很明显......
    【解决方案2】:

    PartialOrdimplemented for references;这里的问题是num::Boundedis not。如果您使 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 特征转换为使用关联类型而不是泛型类型,只需 like Iterator

    Playground

    【讨论】:

    • 感谢您的回答和示例代码,解决方案对我来说似乎非常清晰和明显:) 我接受了上述答案,因为它是第一个答案,但是很好的答案!非常好奇,在此设置中选择关联类型而不是泛型类型的原因/好处是什么?
    • 本书talks about this w.r.t. Iterator -- 将此应用于Argmax&lt;T&gt;,理论上您可以为同一类型实现Argmax&lt;u8&gt;Argmax&lt;char&gt;,然后需要argmax 的每个用户指定T 会是什么。此外,就像Iterator 一样,您不太可能需要在同一类型上实现具有不同输出类型的Argmax
    猜你喜欢
    • 1970-01-01
    • 2021-09-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-23
    • 1970-01-01
    相关资源
    最近更新 更多