首页 > 解决方案 > 有没有更好的功能方法来处理带有错误检查的向量?

问题描述

我正在学习 Rust,想知道如何改进下面的代码。

我有一个形式的元组向量(u32, String)。这些u32值表示行号,Strings 是相应行上的文本。只要所有字符串值都可以成功解析为整数,我想返回一个Ok<Vec<i32>>包含刚刚解析的值的String值,但如果不是,我想返回某种形式的错误(只是Err<String>下面示例中的一个)。

我正在尝试学习避免可变性并在适当的情况下使用功能样式,如果只需要这些,上面的功能就很容易做到。这是我在这种情况下想出的:

fn data_vals(sv: &Vec<(u32, String)>) -> Result<Vec<i32>, String> {
    sv.iter()
        .map(|s| s.1.parse::<i32>()
                    .map_err(|_e| "*** Invalid data.".to_string()))
        .collect()
}

但是,小问题是我想为每个无效值(而不仅仅是第一个)打印一条错误消息,并且错误消息应该包含行号和有问题的元组中的字符串值。

我已经设法用以下代码做到了:

fn data_vals(sv: &Vec<(u32, String)>) -> Result<Vec<i32>, String> {
    sv.iter()
        .map(|s| (s.0, s.1.parse::<i32>()
                  .or_else(|e| {
                      eprintln!("ERROR: Invalid data value at line {}:  '{}'",
                                s.0, s.1);
                      Err(e)
                  })))
        .collect::<Vec<(u32, Result<i32, _>)>>() // Collect here to avoid short-circuit
        .iter()
        .map(|i| i.1
             .clone()
             .map_err(|_e| "*** Invalid data.".to_string()))
        .collect()
}

这可行,但看起来相当混乱和麻烦 - 特别是collect()在中间键入以避免短路,因此所有错误都被打印出来。这个clone()调用也很烦人,我不确定为什么需要它 - 编译器说我要移出借用的内容,但我不确定要移走什么。有没有办法可以更干净地完成?还是我应该回到更程序化的风格?当我尝试时,我最终得到了可变变量和一个指示成功和失败的标志,这似乎不太优雅:

fn data_vals(sv: &Vec<(u32, String)>) -> Result<Vec<i32>, String> {
    let mut datavals = Vec::new();
    let mut success = true;
    for s in sv {
        match s.1.parse::<i32>() {
            Ok(v) => datavals.push(v),
            Err(_e) => {
                eprintln!("ERROR: Invalid data value at line {}:  '{}'",
                          s.0, s.1);
                success = false;
            },
        }
    }
    if success {
        return Ok(datavals);
    } else {
        return Err("*** Invalid data.".to_string());
    }
}

有人可以告诉我最好的方法吗?我应该坚持这里的程序风格吗?如果可以,可以改进吗?还是有更清洁的功能方式来做到这一点?还是两者的混合?任何建议表示赞赏。

标签: functional-programmingrust

解决方案


我认为这就是partition_map()itertools 的用途:

use itertools::{Either, Itertools};

fn data_vals<'a>(sv: &[&'a str]) -> Result<Vec<i32>, Vec<(&'a str, std::num::ParseIntError)>> {
    let (successes, failures): (Vec<_>, Vec<_>) =
        sv.iter().partition_map(|s| match s.parse::<i32>() {
            Ok(v) => Either::Left(v),
            Err(e) => Either::Right((*s, e)),
        });
    if failures.len() != 0 {
        Err(failures)
    } else {
        Ok(successes)
    }
}

fn main() {
    let numbers = vec!["42", "aaaezrgggtht", "..4rez41eza", "55"];
    println!("{:#?}", data_vals(&numbers));
}

推荐阅读