首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >锈蚀计算器

锈蚀计算器
EN

Code Review用户
提问于 2021-10-12 13:11:08
回答 1查看 317关注 0票数 3

为了学习生锈,我决定写一个简单的计算器。这个计算器可以以1+2*3/4-5的形式接受一个字符串,并计算结果。它是这样做的,同时记住了操作的顺序(*/ does +-)。

我想出了以下几点:

代码语言:javascript
复制
/// Token containing either a number or an operation. Never both.
#[derive(Debug, PartialEq)]
enum Token {
    NUMBER(i64),
    /// Operation being one of +/*-
    OPERATION(char),
}

impl Token {
    pub fn from_string(string: &str) -> Result<Self, &str> {
        let number = string.parse::<i64>().ok();
        if number.is_some() {
            Ok(Token::NUMBER(number.unwrap()))
        } else {
            match string.chars().nth(0) {
                Some(c) => Ok(Token::OPERATION(c)),
                None => Err("Can't parse token from empty string."),
            }
        }
    }
}

/// Tokenizes the given string. Splits numbers from non-numbers. Returns a vector of tokens.
fn tokenize(sum: &str) -> Vec<Token> {
    let mut parts: Vec<String> = Vec::new();
    let mut prev_is_number = false;
    for c in sum.chars() {
        if c.is_numeric() != prev_is_number {
            prev_is_number = c.is_numeric();
            parts.push(String::new());
        }
        parts.last_mut().unwrap().push(c);
    }
    let mut tokens: Vec<Token> = Vec::new();
    for token in parts {
        tokens.push(Token::from_string(&token).expect(&format!("Failed to parse {}", token)));
    }
    prev_is_number = false;
    for token in &tokens {
        let is_number = match token {
            Token::NUMBER(..) => true,
            _ => false
        };
        assert_ne!(prev_is_number,
                is_number
        ); // Assert numbers always followed by tokens and otherwise.
        prev_is_number = is_number;
    }

    return tokens;
}

/// Solves the multiplications and divisions in the given token vector. Returns a vector of tokens that were not solved, with the solved parts in-place.
/// e.g. 1+2*3 returns 1+6.
fn solve_multiply_divide(tokens: &Vec<Token>) -> Vec<Token> {
    let mut prev_number: Option<i64> = None;
    let mut prev_operation: Option<char> = None;

    let mut result: Vec<Token> = Vec::new();

    for token in tokens {
        match token {
            Token::NUMBER(number) => {
                match prev_number {
                    None => {
                        prev_number = Some(*number);
                    },
                    Some(unwrapped_prev_number) => {
                        match prev_operation {
                            Some('*') => {
                                prev_number = Some(unwrapped_prev_number * number);
                            },
                            Some('/') => {
                                prev_number = Some(unwrapped_prev_number / number);
                            },
                            _ => {panic!("Found two numbers after each other without operator in between. Incorrect token sequence supplied.")}
                        }
                        prev_operation = None;
                    }
                }
            }
            Token::OPERATION(operation) => {
                if *operation == '*' || *operation == '/' {
                    prev_operation = Some(*operation);
                } else {
                    if prev_number.is_some() {
                        result.push(Token::NUMBER(prev_number.unwrap()));
                    }
                    result.push(Token::OPERATION(*operation));
                    prev_number = None;
                    prev_operation = None;
                }

            }
        }
    }

    if prev_number.is_some() {
        result.push(Token::NUMBER(prev_number.unwrap()));
    }

    return result;
}

/// Solves the plus and minus in the given token vector. Returns a vector of tokens that were not solved, with the solved parts in-place.
/// e.g. 1+2*3 returns 3*3
fn solve_plus_minus(tokens: &Vec<Token>) -> Vec<Token> {
    let mut prev_number: Option<i64> = None;
    let mut prev_operation: Option<char> = None;

    let mut result: Vec<Token> = Vec::new();

    for token in tokens {
        match token {
            Token::NUMBER(number) => {
                match prev_number {
                    None => {
                        prev_number = Some(*number);
                    },
                    Some(unwrapped_prev_number) => {
                        match prev_operation {
                            Some('+') => {
                                prev_number = Some(unwrapped_prev_number + number);
                            },
                            Some('-') => {
                                prev_number = Some(unwrapped_prev_number - number);
                            },
                            _ => {panic!("Found two numbers after each other without operator in between. Incorrect token sequence supplied.")}
                        }
                        prev_operation = None;
                    }
                }
            }
            Token::OPERATION(operation) => {
                if *operation == '+' || *operation == '-' {
                    prev_operation = Some(*operation);
                } else {
                    if prev_number.is_some() {
                        result.push(Token::NUMBER(prev_number.unwrap()));
                    }
                    result.push(Token::OPERATION(*operation));
                    prev_number = None;
                    prev_operation = None;
                }

            }
        }
    }

    if prev_number.is_some() {
        result.push(Token::NUMBER(prev_number.unwrap()));
    }

    return result;
}

/// Solves a given equation.
pub fn solve(sum: &str) -> Result<i64, &str> {
    let tokens = tokenize(sum);
    let tokens_multiplied_divided = solve_multiply_divide(&tokens);
    let tokens_plus_minus = solve_plus_minus(&tokens_multiplied_divided);
    assert_eq!(tokens_plus_minus.len(), 1);
    match tokens_plus_minus.first().unwrap() {
        Token::NUMBER(result) => Ok(*result),
        _ => Err("Could not solve. Invalid equation?")
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// Returns the vector of tokens for 1+2*3/4-5
    fn get_test_tokens() -> Vec<Token> {
        return vec![
            Token::NUMBER(1),
            Token::OPERATION('+'),
            Token::NUMBER(2),
            Token::OPERATION('*'),
            Token::NUMBER(3),
            Token::OPERATION('/'),
            Token::NUMBER(4),
            Token::OPERATION('-'),
            Token::NUMBER(5),
        ];
    }
    #[test]
    fn test_tokenize() {
        assert_eq!(tokenize("1+2*3/4-5"), get_test_tokens());
    }
    #[test]
    fn test_multiply_divide() {
        assert_eq!(
            solve_multiply_divide(&get_test_tokens()),
            [
                Token::NUMBER(1),
                Token::OPERATION('+'),
                Token::NUMBER(2 * 3 / 4),
                Token::OPERATION('-'),
                Token::NUMBER(5),
            ]
        )
    }

    #[test]
    fn test_solve() {
        match solve("1+2*3/4-5") {
            Ok(-3) => assert!(true),
            Ok(number) => assert!(false, "wrong answer: {}", number),
            Err(error) => assert!(false, "got an error: {}", error)
        }
    }
}

这段代码的一个问题是solve_multiply_divide和solve_plus_minus之间的重叠,我觉得它们可能会以某种方式合并,但我还没有弄清楚是如何合并的。

我来自一个C++背景,它可以在这段代码中闪闪发光。我如何改进我的代码,使它更多地遵循一种生疏的思维方式,而不是c++呢?

EN

回答 1

Code Review用户

发布于 2022-01-04 16:12:06

这段代码的一个问题是solve_multiply_divide和solve_plus_minus之间的重叠,我觉得它们可能会以某种方式合并,但我还没有弄清楚是如何合并的。

这可以通过引入一个更通用的函数solve_bin_ops来实现。然后,您将遇到match的一些限制,您可以通过使用if来解决这些限制。如下所示:

代码语言:javascript
复制
/// Solves the multiplications and divisions in the given token vector. Returns a vector of tokens that were not solved, with the solved parts in-place.
/// e.g. 1+2*3 returns 1+6.
fn solve_multiply_divide(tokens: &Vec<Token>) -> Vec<Token> {
    solve_bin_ops(tokens, ('*', i64::wrapping_mul), ('/', i64::wrapping_div))
}

/// Solves the plus and minus in the given token vector. Returns a vector of tokens that were not solved, with the solved parts in-place.
/// e.g. 1+2*3 returns 3*3
fn solve_plus_minus(tokens: &Vec<Token>) -> Vec<Token> {
    solve_bin_ops(tokens, ('+', i64::wrapping_add), ('-', i64::wrapping_sub))
}

fn solve_bin_ops(
    tokens: &Vec<Token>,
    op1: (char, fn(i64, i64) -> i64),
    op2: (char, fn(i64, i64) -> i64),
) -> Vec<Token> {
    let mut prev_number: Option<i64> = None;
    let mut prev_operation: Option<char> = None;

    let mut result: Vec<Token> = Vec::new();

    for token in tokens {
        match token {
            Token::NUMBER(number) => match prev_number {
                None => {
                    prev_number = Some(*number);
                }
                Some(unwrapped_prev_number) => {
                    if prev_operation == Some(op1.0) {
                        prev_number = Some(op1.1(unwrapped_prev_number, *number));
                    } else if prev_operation == Some(op2.0) {
                        prev_number = Some(op2.1(unwrapped_prev_number, *number));
                    } else {
                        panic!("Found two numbers after each other without operator in between. Incorrect token sequence supplied.")
                    }
                    prev_operation = None;
                }
            },
            Token::OPERATION(operation) => {
                if *operation == op1.0 || *operation == op2.0 {
                    prev_operation = Some(*operation);
                } else {
                    if prev_number.is_some() {
                        result.push(Token::NUMBER(prev_number.unwrap()));
                    }
                    result.push(Token::OPERATION(*operation));
                    prev_number = None;
                    prev_operation = None;
                }
            }
        }
    }

    if prev_number.is_some() {
        result.push(Token::NUMBER(prev_number.unwrap()));
    }

    result
}

我在这里稍微修改了二进制运算符,指定了在溢出情况下应该发生什么。要恢复原始代码的行为(在调试模式下溢出时会出现恐慌)(因为您还没有指定在这种情况下希望发生的情况),请执行以下操作:

代码语言:javascript
复制
/// Solves the multiplications and divisions in the given token vector. Returns a vector of tokens that were not solved, with the solved parts in-place.
/// e.g. 1+2*3 returns 1+6.
fn solve_multiply_divide(tokens: &Vec<Token>) -> Vec<Token> {
    solve_bin_ops(tokens, ('*', <i64 as std::ops::Mul>::mul), ('/', <i64 as std::ops::Div>::div))
}

/// Solves the plus and minus in the given token vector. Returns a vector of tokens that were not solved, with the solved parts in-place.
/// e.g. 1+2*3 returns 3*3
fn solve_plus_minus(tokens: &Vec<Token>) -> Vec<Token> {
    solve_bin_ops(tokens, ('+', <i64 as std::ops::Add>::add), ('-', <i64 as std::ops::Sub>::sub))
}

在您接受的语言(例如(3+4)*2)中引入显式分组(通常使用括号)可能会改变您的设计。

票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/268907

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档