Regex
(\d+)\s?\+\s?(\d+)
Es momento de desarrollar tu primera gran aplicación en Rust. Eres libre de idear y programar una aplicación de consola como una calculadora o un calendario. Para esto, ten presenta algunos consejos:
Utilización de dependencias en Rust
Explora la gran cantidad de dependencias que tiene Rust en su repositorio para encontrar las que necesites para tu app.
Algunas dependencias interesantes:
Recuerda agregar la dependencia en el archivo Cargo.toml para su posterior descarga e importarla con use.
use regex::Regex; fn main() { // ... }
Cada dependencia tiene su propia documentación para que sepas cómo utilizarla.
Conversiones de datos en Rust
Veamos algunas formas de convertir datos en Rust que te servirá de ahora en adelante.
String a entero
Hay varias formas de declarar una variable del tipo String:
let nombre = "123".to_string(); let nombre: String = "123".to_string(); let nombre = String::from("123"); let mut nombre: String = String::new(); nombre = "123".to_string();
Conviértelo a número entero con parse(), pero teniendo en cuenta de borrar los espacios en blanco con trim() para evitar inconvenientes y capturar los errores con unwrap().
let number: i32 = nombre.trim().parse().unwrap(); println!("{}", number+1);
String vs. &str
Recuerda que el tipo de dato String permite manipular una cadena de texto, mientras que el tipo de dato &str contiene la referencia a un String, pero solo contiene su valor.
Cuando le asignas a una variable un valor con dobles comillas "", las mismas crean un &str, por esto utilizamos to_string() para que retorne el tipo de dato en formato String y poder manipularlo. Y por este mismo motivo necesitas un String para hacer un .trim().parse() y convertir la variable al tipo entero.
Para convertir un String a &str, solo agrega el & (Ampersand) delante del nombre de la variable o con as_str().
let nombre: String = "123".to_string(); let nombre_plano: &str = &nombre; let nombre: String = "123".to_string(); let nombre_plano: &str = nombre.as_str();
Muchas formas de hacer lo mismo ¿No te parece?
TIP: Por convencion, en Rust, todas las variables y nombres de funciones que declares utilizan snackcase, o sea, un
_para separar las palabras. EJ:my_variableomy_function.
Rust posee una curva de aprendizaje algo más compleja cuando se trata de tipos de datos y conversiones. Como siempre, la práctica hará que puedas comprender cuándo utilizar cada tipo y ser más veloz resolviendo problemas.
Contribución creada por: Kevin Fiorentino.
Es momento de desarrollar tu primera gran aplicación en Rust. Eres libre de idear y programar una aplicación de consola como una calculadora o un calendario. Para esto, ten presenta algunos consejos:
Explora la gran cantidad de dependencias que tiene Rust en su repositorio para encontrar las que necesites para tu app.
Algunas dependencias interesantes:
Recuerda agregar la dependencia en el archivo Cargo.toml para su posterior descarga e importarla con use.
use regex::Regex;
fn main() {
// ...
}
Cada dependencia tiene su propia documentación para que sepas cómo utilizarla.
Veamos algunas formas de convertir datos en Rust que te servirá de ahora en adelante.
Hay varias formas de declarar una variable del tipo String:
let nombre = "123".to_string();
let nombre: String = "123".to_string();
let nombre = String::from("123");
let mut nombre: String = String::new();
nombre = "123".to_string();
Conviértelo a número entero con parse(), pero teniendo en cuenta de borrar los espacios en blanco con trim() para evitar inconvenientes y capturar los errores con unwrap().
let number: i32 = nombre.trim().parse().unwrap();
println!("{}", number+1);
Recuerda que el tipo de dato String permite manipular una cadena de texto, mientras que el tipo de dato &str contiene la referencia a un String, pero solo contiene su valor.
Cuando le asignas a una variable un valor con dobles comillas "", las mismas crean un &str, por esto utilizamos to_string() para que retorne el tipo de dato en formato String y poder manipularlo. Y por este mismo motivo necesitas un String para hacer un .trim().parse() y convertir la variable al tipo entero.
Para convertir un String a &str, solo agrega el & (Ampersand) delante del nombre de la variable o con as_str().
let nombre: String = "123".to_string();
let nombre_plano: &str = &nombre;
let nombre: String = "123".to_string();
let nombre_plano: &str = nombre.as_str();
Muchas formas de hacer lo mismo ¿No te parece?
TIP: Por convencion, en Rust, todas las variables y nombres de funciones que declares utilizan snackcase, o sea, un
_para separar las palabras. EJ:my_variableomy_function.
Rust posee una curva de aprendizaje algo más compleja cuando se trata de tipos de datos y conversiones. Como siempre, la práctica hará que puedas comprender cuándo utilizar cada tipo y ser más veloz resolviendo problemas.
Contribución creada por: Kevin Fiorentino.
Regex
(\d+)\s?\+\s?(\d+)
Esta es mi solucion, con una funcion para que no se repita tanto el codigo:
use regex::Regex;
fn make_operation(reg: Regex, mut expresion: String, operation: &str) -> String {
if operation.is_empty() {
return "".to_string();
}
loop {
//Aplicar operaciones
let caps = reg.captures(expresion.as_str());
if caps.is_none() {
break;
}
let caps = caps.unwrap();
let cap_expresion = caps.get(0).unwrap().as_str();
let left_value: i32 = caps.get(1).unwrap().as_str().parse().unwrap();
let right_value: i32 = caps.get(2).unwrap().as_str().parse().unwrap();
let result = match operation {
"+" => left_value + right_value,
"-" => left_value - right_value,
"*" => left_value * right_value,
"/" => left_value / right_value,
_ => 0,
};
expresion = expresion.replace(cap_expresion, &result.to_string());
}
expresion
}
fn main() {
//Regex
let re_add = Regex::new(r"(\d+)\s?\+\s?(\d+)").unwrap();
let re_less = Regex::new(r"(\d+)\s?\-\s?(\d+)").unwrap();
let re_mult = Regex::new(r"(\d+)\s?\*\s?(\d+)").unwrap();
let re_div = Regex::new(r"(\d+)\s?/\s?(\d+)").unwrap();
//Traer datos del usuario
println!("Por favor introduce tu expresion");
let mut expresion = String::new();
std::io::stdin().read_line(&mut expresion).unwrap();
//Multiplicacion
expresion = make_operation(re_mult, expresion, "*");
//Division
expresion = make_operation(re_div, expresion, "/");
//Suma
expresion = make_operation(re_add, expresion, "+");
//Resta
expresion = make_operation(re_less, expresion, "-");
//Mostrar resultados
println!("Resultados: {}", expresion);
}
regex que incluye todas las operaciones aritméticas
(\d+)\s+?(\+|\*|/|\-)\s+?(\d+)
calculadora científica ??
Mi código repo le agregué enums para controlar de mejor forma las operaciones.
I think Platzi must clarify this exercise because the teacher's implementation is wrong.
Aqui dejo mi pequeño aporte:
use regex::Regex;
fn main() {
println!("Calculadora");
// Regex
let re_add = Regex::new(r"(\d+)\s?([\+\-])\s?(\d+)").unwrap();
let re_mult = Regex::new(r"(\d+)\s?([/\*])\s?(\d+)").unwrap();
// Traer datos del usuario
println!("Ingrese una operacion: ");
let mut expression = String::new();
std::io::stdin().read_line(&mut expression).unwrap();
// Multiplicacion y Division
loop {
let caps = re_mult.captures(expression.as_str());
if caps.is_none() {
break;
}
let caps = caps.unwrap();
let cap_expression = caps.get(0).unwrap().as_str();
let left_value: i32 = caps.get(1).unwrap().as_str().parse().unwrap();
let right_value: i32 = caps.get(3).unwrap().as_str().parse().unwrap();
let sign = caps.get(2).unwrap().as_str();
if sign == "*" {
let result = left_value * right_value;
expression = expression.replace(cap_expression, &result.to_string());
} else if sign == "/" {
let result = left_value / right_value;
expression = expression.replace(cap_expression, &result.to_string());
}
}
// Suma y resta
loop {
let caps = re_add.captures(expression.as_str());
if caps.is_none() {
break;
}
let caps = caps.unwrap();
let cap_expression = caps.get(0).unwrap().as_str();
let left_value: i32 = caps.get(1).unwrap().as_str().parse().unwrap();
let right_value: i32 = caps.get(3).unwrap().as_str().parse().unwrap();
let sign = caps.get(2).unwrap().as_str();
if sign == "+" {
let result = left_value + right_value;
expression = expression.replace(cap_expression, &result.to_string());
} else if sign == "-" {
let result = left_value - right_value;
expression = expression.replace(cap_expression, &result.to_string());
}
}
// Mostrar resultado
println!("Resultado: {}", expression);
}
Quizá para este ejercicio en particular hubiese preferido usar parser combinators, pareciese que el crate nom es bastante popular en la comunidad Rust. Pero para seguir lo indicado en esta clase seguí usando expresiones regulares.
En la siguiente solución podrán apreciar un par de cosas:
r"(?P<left>\d+)\s?\+\s?(?P<right>\d+)". Me parece que el código queda un poco más legible de esta manera, al menos, al momento de acceder a la captura en específico parece más claro.fold para reducir la expresión originalAcá el módulo principal:
use regex::Regex;
fn main() {
println!("expression: ");
let mut expression = String::new();
std::io::stdin().read_line(&mut expression).unwrap();
println!("Result: {}", reduce_expression(expression))
}
fn reduce_expression(mut expression: String) -> String {
let re_add = Regex::new(r"(?P<left>\d+)\s?\+\s?(?P<right>\d+)").unwrap();
let re_sub = Regex::new(r"(?P<left>\d+)\s?\-\s?(?P<right>\d+)").unwrap();
let re_mult = Regex::new(r"(?P<left>\d+)\s?\*\s?(?P<right>\d+)").unwrap();
let re_div = Regex::new(r"(?P<left>\d+)\s?/\s?(?P<right>\d+)").unwrap();
let precedence = [("/", re_div), ("*", re_mult), ("+", re_add), ("-", re_sub)];
expression = precedence
.into_iter()
.fold(expression, |acc, (operator, re)| apply(re, acc, operator));
expression
}
// apply the given operation
fn apply(re: Regex, mut expression: String, operator: &str) -> String {
loop {
let caps = re.captures(expression.as_str());
if caps.is_none() {
break;
}
let caps = caps.unwrap();
let cap_expression = caps.get(0).unwrap().as_str();
let left_value: i32 = caps.name("left").unwrap().as_str().parse().unwrap();
let right_value: i32 = caps.name("right").unwrap().as_str().parse().unwrap();
let result = match operator {
"+" => left_value + right_value,
"-" => left_value - right_value,
"*" => left_value * right_value,
"/" => left_value / right_value,
_ => 0,
};
expression = expression.replace(cap_expression, &result.to_string());
}
expression
}
Y sus correspondientes pruebas unitarias, en realidad hice una sola por aquello de la pereza.
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_apply() {
let expression: String = "12/2*4+1*12-32".to_string();
assert_eq!(reduce_expression(expression), "4");
}
}
Pienso que un pequeño cambio en la regex podría aportar más flexibilidad al usuario: en lugar de utilizar el metachar \s? se puede utilizar \s* para que si el usuario ingresa 10 + 20 , igual tome los valores correspondientes
Aquí dejo mi aporte, todo esta estructurado en un main, en próximas sesiones lo hare mediante funciones. Le hice una prueba intensiva de escritorio. Les dejo el link del github.
Calculadora