【问题标题】:What is a good way to match strings against patterns and extract values?将字符串与模式匹配并提取值的好方法是什么?
【发布时间】:2018-11-03 10:28:38
【问题描述】:

我正在尝试得到这样的东西(不起作用):

match input {
    "next" => current_question_number += 1,
    "prev" => current_question_number -= 1,
    "goto {x}" => current_question_number = x,
    // ...
    _ => status = "Unknown Command".to_owned()
}

我尝试了两个不同版本的Regex

go_match = regex::Regex::new(r"goto (\d+)?").unwrap();
// ...
match input {
    ...
    x if go_match.is_match(x) => current_question_number = go_match.captures(x).unwrap().get(1).unwrap().as_str().parse().unwrap(),
    _ => status = "Unknown Command".to_owned()
}

let cmd_match = regex::Regex::new(r"([a-zA-Z]+) (\d+)?").unwrap();
// ...
if let Some(captures) = cmd_match.captures(input.as_ref()) {
        let cmd = captures.get(1).unwrap().as_str().to_lowercase();
        if let Some(param) = captures.get(2) {
            let param = param.as_str().parse().unwrap();
            match cmd.as_ref() {
                "goto" => current_question_number = param,
            }
        } else {
            match cmd.as_ref() {
                "next" => current_question_number += 1,
                "prev" => current_question_number -= 1,
            }
        }
    } else {
        status = "Unknown Command".to_owned();
    }

这两种方法似乎都是一种非常冗长和复杂的方法来做一些很常见的事情,我错过了什么吗?

【问题讨论】:

    标签: string rust pattern-matching


    【解决方案1】:

    你有一种选择问题的迷你语言:

    • 选择下一个问题
    • 选择上一个问题
    • 转到一个特定的问题

    如果您的要求在此结束,那么基于 Regex 的解决方案非常适合。

    如果您的 DSL 可能发展出基于解析器的解决方案,则值得考虑。

    解析器组合器nom 是从基本元素开始构建语法的强大工具。

    您的语言具有以下特点:

    • 它有三个备选语句 (alt!):nextprevgoto \d+

    • 最复杂的语句“goto {number}”是由关键字 (tag!) goto 在 (preceded!) 前面的一个 number (digit!)。

    • 必须忽略任意数量的空格 (ws!)

    这些要求在此实现中转化:

    #[macro_use]
    extern crate nom;
    
    use nom::{IResult, digit};
    use nom::types::CompleteStr;
    
    // we have for now two types of outcome: absolute or relative cursor move
    pub enum QMove {
        Abs(i32),
        Rel(i32)
    }
    
    pub fn question_picker(input: CompleteStr) -> IResult<CompleteStr, QMove> {
        ws!(input,
            alt!(
                map!(
                    tag!("next"),
                    |_| QMove::Rel(1)
                ) |
                map!(
                    tag!("prev"),
                    |_| QMove::Rel(-1)
                ) |
                preceded!(
                    tag!("goto"),
                    map!(
                        digit,
                        |s| QMove::Abs(std::str::FromStr::from_str(s.0).unwrap())
                    )
                )
            )
        )
    }
    
    fn main() {
        let mut current_question_number = 60;
        let first_line = "goto 5";
    
        let outcome = question_picker(CompleteStr(first_line));
    
        match outcome {
            Ok((_, QMove::Abs(n))) => current_question_number = n,
            Ok((_, QMove::Rel(n))) => current_question_number += n,
            Err(err) => {panic!("error: {:?}", err)}
        }
    
        println!("Now at question {}", current_question_number);
    }
    

    【讨论】:

    • 正则表达式答案对我有用,但这看起来确实很有趣(以前从未遇到过 nom)。
    【解决方案2】:

    您可以创建一个主Regex 来捕获所有有趣的组件,然后构建一个所有捕获的组件的Vec。这个Vec 然后可以匹配:

    extern crate regex;
    
    use regex::Regex;
    
    fn main() {
        let input = "goto 4";
        let mut current_question_number = 0;
    
        // Create a regex that matches on the union of all commands
        // Each command and argument is captured
        // Using the "extended mode" flag to write a nicer Regex
        let input_re = Regex::new(
            r#"(?x)
            (next) |
            (prev) |
            (goto)\s+(\d+)
            "#
        ).unwrap();
    
        // Execute the Regex
        let captures = input_re.captures(input).map(|captures| {
            captures
                .iter() // All the captured groups
                .skip(1) // Skipping the complete match
                .flat_map(|c| c) // Ignoring all empty optional matches
                .map(|c| c.as_str()) // Grab the original strings
                .collect::<Vec<_>>() // Create a vector
        });
    
        // Match against the captured values as a slice
        match captures.as_ref().map(|c| c.as_slice()) {
            Some(["next"]) => current_question_number += 1,
            Some(["prev"]) => current_question_number -= 1,
            Some(["goto", x]) => {
                let x = x.parse().expect("can't parse number");
                current_question_number = x;
            }
            _ => panic!("Unknown Command: {}", input),
        }
    
        println!("Now at question {}", current_question_number);
    }
    

    【讨论】:

      【解决方案3】:

      您可以为此使用str::split (playground)

      fn run(input: &str) {
          let mut toks = input.split(' ').fuse();
          let first = toks.next();
          let second = toks.next();
      
          match first {
              Some("next") => println!("next found"),
              Some("prev") => println!("prev found"),
              Some("goto") => match second {
                  Some(num) => println!("found goto with number {}", num),
                  _ => println!("goto with no parameter"),
              },
              _ => println!("invalid input {:?}", input),
          }
      }
      
      fn main() {
          run("next");
          run("prev");
          run("goto 10");
          run("this is not valid");
          run("goto"); // also not valid but for a different reason
      }
      

      会输出

      next found
      prev found
      found goto with number 10
      invalid input "this is not valid"
      goto with no parameter
      

      【讨论】:

      • 你需要再添加一层来检查第二个参数是否为数字,我想这没问题,但如果我想我仍然需要有很多重复的代码有多个命令,如cmd num,如果我需要更多参数,情况会更糟。
      • @Shepmaster 完全同意,我添加了保险丝。我以后肯定会更多地使用它,感谢您指出这一点
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-06-25
      • 1970-01-01
      • 1970-01-01
      • 2019-12-03
      • 2017-03-29
      • 2011-12-10
      • 2017-05-26
      相关资源
      最近更新 更多