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

huangapple go评论58阅读模式
英文:

How to propagate `Result` through `.max_by_key()`

问题

// 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> {
    // Use the `max_by_key` method with a custom closure to compare `Result` values.
    return v.iter().max_by_key(|i| {
        match float_or_err(i) {
            Ok(result) => result,
            Err(_) => OrderedFloat(std::f64::NEG_INFINITY), // Return the smallest value for errors
        }
    });
}

This code defines the max_by_float_or_err function, which uses the max_by_key method with a custom closure to compare the Result values returned by float_or_err(i). If float_or_err(i) returns an Ok value, it uses that value for comparison. If it returns an Err value, it uses OrderedFloat(std::f64::NEG_INFINITY) as a sentinel value to represent the smallest possible value for errors, ensuring that errors are propagated correctly while finding the maximum value.

英文:

I have a function float_or_err(i) that takes an integer and returns either a float or an error. I want to write a function that takes an array of integers and returns the one for which float_or_err(i) is greatest, or propagates the error if float_or_err() errors on any of the is.

Now, I have read Using max_by_key on a vector of floats [duplicate] and I understand why Rust doesn't implement a total ordering on floats, and I have decided to use the ordered_float crate instead of f64 to impose a total ordering on the results of float_or_err().

However, that question does not address the case where the argument to .max_by_key() is a closure that returns a Result&lt;OrderedFloat&lt;f64&gt;, _&gt;, as in the following example:

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: &amp;isize) -&gt; Result&lt;OrderedFloat&lt;f64&gt;, ErrorKind&gt; {
    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&lt;isize&gt;) -&gt; Option&lt;&amp;isize&gt; {
    return v.iter().max_by_key(|i| float_or_err(i));
}

The compiler does not accept the second-to-last line, because

the trait bound `ErrorKind: Ord` is not satisfied
the trait `Ord` is implemented for `Result&lt;T, E&gt;`

OK, looks like I need to do something like

impl Ord for Result&lt;OrderedFloat&lt;f64&gt;, ErrorKind&gt; {
    // ...
}

(where I order first by the float, then use the ErrorKind as a tiebreaker.)

But the compiler won't let me implement Ord for a type that I didn't create:

`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

Actually, my first intuition is to write:

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

But this doesn't compile either, because you can't use the question mark operator inside of a closure. I have read Alternatives for using the question mark operator inside a map function closure, where the suggested solution is to just to move the question mark to the end of the chain, but that solution is particular to the function in question, where the reduce op is .sum() instead of .max_by_key(), and .sum() is implemented natively for Result. This is not the case for .max_by_key() (and many other reduce ops).

  • In the context of the max_by_float_or_err() function defined above, how can I propogate errors from float_or_err()?
  • I am looking for a solution that uses .max_by_key() (or at least another reduce op) instead of rewriting the body as a for loop.

Edit: I also read How do I implement a trait I don't own for a type I don't own?, which suggests something like:

struct MyResult(Result&lt;OrderedFloat&lt;f64&gt;, ErrorKind&gt;);

impl Ord for MyResult {
    // ...
}

This solution is unsatisfactory because instances of MyResult don't seem to inherit all the methods associated with normal Results. For example, I cannot write:

let my_result: OrderedFloat&lt;f64&gt; = float_or_err(-1).unwrap_or_default(6.28);

答案1

得分: 3

问题在于您希望在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可能会稍微简单一些。

英文:

The issue here is that you are looking to short-circuit on an Err, which the max_by_key function is not capable of. I would probably make this in a for loop, but this is possible using a short-circuiting method such as try_fold.

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

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

    Some(max).transpose()
}

In the future, this would be slightly simpler with try_reduce

答案2

得分: 2

这里是一个传播错误的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))
}

请注意,这是Rust代码的翻译。

英文:

Here's a generic version of max_by_key() that propagates errors:

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

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

    Ok(Some(curr))
}

huangapple
  • 本文由 发表于 2023年5月29日 23:26:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/76358541.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定