【问题标题】:Alternatives for using the question mark operator inside a map function closure在 map 函数闭包中使用问号运算符的替代方法
【发布时间】:2023-04-06 09:41:02
【问题描述】:

在这个函数中parse 可以返回错误,所以我使用.filter_map(Result::ok) 过滤掉它们。

fn part1(input: &str) {
     let sum = input.lines()
        .map(|l| l.parse::<u32>())
        .filter_map(Result::ok)
        .map(|n| n as f32 / 3.0)
        .map(|f| f.round())
        .map(|f| f as u32 - 2)
        .sum::<u32>();
    // println!("{}", sum);
    println!("{:?}", sum);
}

但是,当 parse 出现错误时,我想退出 part1 函数,有点像使用像 .map(|l| l.parse::&lt;u32&gt;()?) 这样的问号运算符。如果这样做,编译器会给出错误

error[E0277]: the `?` operator can only be used in a closure that returns `Result` 
or `Option` (or another type that implements `std::ops::Try`)                      
  --> src/main.rs:64:18                                                            
   |                                                                               
64 |         .map(|l| l.parse::<u32>()?)                                           
   |              ----^^^^^^^^^^^^^^^^^                                            
   |              |   |                                                            
   |              |   cannot use the `?` operator in a closure that returns `u32`  
   |              this function should return `Result` or `Option` to accept `?`

这是因为问号运算符在闭包内使用,所以它从闭包而不是封闭函数返回?在闭包内使用问号运算符有哪些惯用的替代方法,以便如果 parse 给出错误,我可以从 part1 返回,或者如果解析成功,则打开 Ok?结果应该类似于.filter_map(Result::ok),除了它不会过滤掉错误,而是会在出现错误时从封闭函数中返回。

【问题讨论】:

  • 附注:与 Chris 相比,恕我直言,拥有多个 map 电话比拥有一个大块更好。所以保留它;)
  • @hellow 删除对 map 的调用可悲地摆脱了计算的管道感觉,这几乎使它感觉更清晰。但我坚持我的建议,因为我更喜欢计算不与迭代器方法捆绑在一起。不过我明白你的意思!
  • Rust by example 提供了一些很好的模式:doc.rust-lang.org/rust-by-example/error/iter_result.html

标签: rust


【解决方案1】:

您可以继续将Resultparse 传递到更远的链中,并允许最终的sum 工作 - 因为Sum 是为Result 实现的。然后你可以在链的最终结果上使用?

示例如下所示:

fn part1(input: &str) -> Result<u32,std::num::ParseIntError>  {
     let sum = input.lines()
        .map(|l| l.parse::<u32>())
        .map(|n| n.map( |n| n as f32 / 3.0) )
        .map(|f| f.map( |f| f.round() ) )
        .map(|f| f.map( |f| f as u32 - 2) )
        .sum::<Result<u32,_>>()?;
    Ok(sum)
}

如果您使用 nightly rust,您可以使用 try 块摆脱嵌套闭包

#![feature(try_blocks)]

fn part1(input: &str) -> Result<u32, std::num::ParseIntError> {
    let sum = input.lines()
       .map( |l| try {
            let n = l.parse::<u32>()?;
            let f = n as f32 / 3.0;
            let f = f.round();
            f as u32 - 2
       })
       .sum::<Result<u32,_>>()?;
    Ok(sum)
}

如果您不使用 nightly,则可以将处理提取到返回 Result 的闭包中。

fn part1(input: &str) -> Result<u32, std::num::ParseIntError> {
    let process_line = |l:&str| -> Result<u32,std::num::ParseIntError> {
        let n = l.parse::<u32>()?;
        let f = n as f32 / 3.0;
        let f = f.round();
        Ok(f as u32 - 2)
    };
    let sum = input.lines().map(process_line).sum::<Result<u32,_>>()?;
    Ok(sum)
}

我还假设您的实际用例比您在此处介绍的要复杂一些。对于这么简单的事情,我只需使用 for 循环

fn part1(input: &str) -> Result<u32,std::num::ParseIntError> {
  let mut sum = 0;
  for line in input.lines() {
     let n = l.parse::<u32>()?;
     let f = n as f32 / 3.0;
     let f = f.round();
     sum += f as u32 - 2;
  }
  Ok(sum)
}

【讨论】:

  • 这可能是累积元素的替代方法,try_fold:play.rust-lang.org/…,因为只遍历所有值 try_for_each 就可以了
【解决方案2】:

多次调用map 可能会使某些解决方案显得杂乱无章。

相反,您可以在一次调用map 中执行所有数学运算,然后与sum 一起使用:

fn part1(input: &str) -> Result<(), std::num::ParseIntError> {
     let sum = input.lines()
        .map(|l| {
            let n = l.parse::<u32>()?;
            let mut f = n as f32 / 3.0;
            f = f.round();
            Ok(f as u32 - 2)
        })
        .sum::<Result<u32, _>>()?;

    // println!("{}", sum);
    println!("{:?}", sum);
    Ok(())
}

但是您可以通过删除? 并在Result 上使用map 来更进一步。如果你这样做并从你的函数返回一个值,你甚至不需要sum的显式类型参数:

fn part1(input: &str) -> Result<u32, std::num::ParseIntError> {
     input.lines()
        .map(|l| {
            l.parse::<u32>().map(|n| {
                let mut f = n as f32 / 3.0;
                f = f.round();
                f as u32 - 2
            })
        })
        .sum()
}

然后您必须在函数外部调用println

如果您不喜欢嵌套闭包,您可以随时将数学运算提取到另一个函数(使用更好的名称):

fn part1(input: &str) -> Result<u32, std::num::ParseIntError> {
     input.lines()
        .map(|l| l.parse().map(math_part))
        .sum()
}

fn math_part(n: u32) -> u32 {
    let mut f = n as f32 / 3.0;
    f = f.round();
    f as u32 - 2
}

【讨论】:

  • 有时一个let mut sum = 0; 和一个简单无聊的for 循环是最好的解决方案。
猜你喜欢
  • 2011-12-29
  • 2013-06-17
  • 1970-01-01
  • 1970-01-01
  • 2020-02-24
  • 2022-01-14
  • 2017-07-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多