为了学习生锈,我决定写一个简单的计算器。这个计算器可以以1+2*3/4-5的形式接受一个字符串,并计算结果。它是这样做的,同时记住了操作的顺序(*/ does +-)。
我想出了以下几点:
/// 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++呢?
发布于 2022-01-04 16:12:06
这段代码的一个问题是solve_multiply_divide和solve_plus_minus之间的重叠,我觉得它们可能会以某种方式合并,但我还没有弄清楚是如何合并的。
这可以通过引入一个更通用的函数solve_bin_ops来实现。然后,您将遇到match的一些限制,您可以通过使用if来解决这些限制。如下所示:
/// 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
}我在这里稍微修改了二进制运算符,指定了在溢出情况下应该发生什么。要恢复原始代码的行为(在调试模式下溢出时会出现恐慌)(因为您还没有指定在这种情况下希望发生的情况),请执行以下操作:
/// 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)中引入显式分组(通常使用括号)可能会改变您的设计。
https://codereview.stackexchange.com/questions/268907
复制相似问题