rust 如何通过`.max_by_key()`传播`Result`

gt0wga4j  于 2023-06-06  发布在  其他
关注(0)|答案(2)|浏览(228)

我有一个函数float_or_err(i),它接受一个整数并返回一个浮点数或一个错误。我想写一个函数,它接受一个整数数组,并返回float_or_err(i)最大的那个,或者如果float_or_err()在任何i上出错,则传播错误。
现在,我已经阅读了Using max_by_key on a vector of floats [duplicate],我明白了为什么Rust没有在浮点数上实现全排序,我决定使用ordered_float而不是f64来对float_or_err()的结果施加全排序。
然而,这个问题并没有解决.max_by_key()的参数是一个返回Result<OrderedFloat<f64>, _>的闭包的情况,如下面的例子所示:

use ordered_float::OrderedFloat;

fn main() {}

enum ErrorKind {
    ValidationError
}

// Function of `i` that normally returns an f64 but sometimes has an error
fn float_or_err(i: &isize) -> Result<OrderedFloat<f64>, ErrorKind> {
    if i % 5 == 0 {
        return Err(ErrorKind::ValidationError);
    }
    else {
        return Ok(OrderedFloat(3.14));
    }
}

// Given a vec of integers `i`, find the one for which `float_or_err(i)` is 
// largest. If any of those integers `i` yields an error in `float_or_err(i)`,
// return the error.
fn max_by_float_or_err(v: Vec<isize>) -> Option<&isize> {
    return v.iter().max_by_key(|i| float_or_err(i));
}

编译器不接受倒数第二行,因为

the trait bound `ErrorKind: Ord` is not satisfied
the trait `Ord` is implemented for `Result<T, E>`

好吧,看起来我需要做一些

impl Ord for Result<OrderedFloat<f64>, ErrorKind> {
    // ...
}

(我首先通过浮动排序,然后使用ErrorKind作为决胜局。)
但是编译器不允许我为一个不是我创建的类型实现Ord

`Result` is not defined in the current craterustcE0117
main.rs(9, 1): original diagnostic
only traits defined in the current crate can be implemented for types defined outside of the crate

实际上,我的第一直觉是这样写的:

fn argmax_by_float_or_err(v: Vec<isize>) -> Option<&isize> {
    return v.iter().max_by_key(|i| float_or_err(i)?);
}

但是这也不能编译,因为你不能在闭包中使用问号操作符。我读过Alternatives for using the question mark operator inside a map function closure,其中建议的解决方案是将问号移动到链的末尾,但该解决方案特定于所讨论的函数,其中reduce op是.sum()而不是.max_by_key(),并且.sum()是针对Result的本地实现。.max_by_key()(以及许多其他reduce操作)的情况并非如此。

  • 在上面定义的max_by_float_or_err()函数的上下文中,如何从float_or_err()传播错误?
  • 我正在寻找一个使用.max_by_key()(或至少另一个reduce操作)的解决方案,而不是将主体重写为for循环。
  • 编辑:* 我也读了How do I implement a trait I don't own for a type I don't own?,它建议类似于:
struct MyResult(Result<OrderedFloat<f64>, ErrorKind>);

impl Ord for MyResult {
    // ...
}

这个解决方案并不令人满意,因为MyResult的示例似乎没有继承与普通Result s相关联的所有方法。例如,我不能写:

let my_result: OrderedFloat<f64> = float_or_err(-1).unwrap_or_default(6.28);
kognpnkq

kognpnkq1#

这里的问题是,您希望在Err上短路,而max_by_key函数无法实现。我可能会在for循环中这样做,但这也可以使用短路方法,如try_fold

pub fn max_by_float_or_err(v: &[isize]) -> Result<Option<&isize>, ErrorKind> {
    let Some((first, rest)) = v.split_first() else { return Ok(None) };
    let initial = (first, float_or_err(first)?);

    let max: Result<&isize, ErrorKind> = rest
        .iter()
        .try_fold(initial, |(acc, acc_key), item| {
            let item_key = float_or_err(item)?;
            if acc_key > item_key {
                Ok((acc, acc_key))
            } else {
                Ok((item, item_key))
            }
        })
        .map(|(item, _key)| item);

    Some(max).transpose()
}

将来,使用try_reduce会稍微简单一些

olmpazwi

olmpazwi2#

下面是一个传播错误的max_by_key()的通用版本:

fn try_max_by_key<I, F, K, E>(mut iter: I, mut f: F) -> Result<Option<I::Item>, E>
where
    I: Iterator,
    F: FnMut(&I::Item) -> Result<K, E>,
    K: Ord,
{
    let Some(mut curr) = iter.next() else { return Ok(None) };
    let mut curr_key = f(&curr)?;

    for next in iter {
        let next_key = f(&next)?;
        if next_key > curr_key {
            curr = next;
            curr_key = next_key;
        }
    }

    Ok(Some(curr))
}

相关问题