use std::io;
use std::io::Write;
fn main() {
let mut input = String::new();
let mut input1 = String::new();
print! ("Introduce dos numerados separados por un espacio? ");
let _= io::stdout().flush();
io::stdin().read_line(&mut input).unwrap();
let mut a1 = input.split_whitespace().map(|s| s.parse::<i32>());
let (n, t) = (a1.next().unwrap().unwrap(), a1.next().unwrap().unwrap());
println!("Has introducido {} y {}", n, t);
println! ("Introduce 10 numeros separados por espacios ? ");
io::stdin().read_line(&mut input1).unwrap();
let a1 = input1.split_whitespace().map(|s| s.parse::<i32>());
for i in a1 { print!( "{} ", i.unwrap());}; println!();
}La versión Rust utiliza la opción de trocear la cadena de caracteres
con el método split de la clase String. Además
para la lectura del array, utiliza la función map para
convertir a enteros de 32 bits cada una de las cadenas que nos devuelve
split.
Te preguntarás que son todas esas llamadas a
unwrapen el programa… ya, yo también me lo pregunté en su día. Rust usa el valor enumeradoOptiony su versión mejoradaResult. Estos tipos permiten representar situaciones en las que tenemos que trabajar con un valor… o con nada. El enumeradoOptionpuede contener el valorNoneoSomevalor. Así primero podemos saber si tenemos valor o no, y luego ya acceder a ese valor.Esto último se puede hacer con el método
unwrapque extraerá el valor que nos interesa del valorOptiono utilizando la palabra clavematchque nos permite machear el valorNonedirectamente.unwrapes equivalente a un bloquematch, en el que o retornamos el valor o invocamospanicdirectamente que normalmente terminará el programa.
Resultsigue el mismo concepto pero cuando necesitamos retornar códigos de error. Este tipo define dos variantesOk(T)(no error y hay un valor que recuperar) oErr(E)(se ha producido un error, y el valor que obtendrás estará relacionado con el error).
La línea que realiza todo el trabajo es:
let a1 = input1.split_whitespace().map(|s| s.parse::<i32>());Esta línea genera un iterador para recorrer las subcadenadas
generadas a partir de separar la primera cadena por los espacios en
blanco, para luego aplicar el método parse a cada una de
esas subcadenas y así convertir cada una de ellas a entero. El código es
más o menos similar a:
let mut a1 = Vec::new();
for val in input1.split_whitespace() {
a1.push (val.parse::<i32>())
}Aunque no exactamente ya que los tipos son diferentes. En el primer
caso a1 es un iterador mientras que en el segundo estamos
generando un vector de Result. La diferencia es sutil,
mientras en este último ejemplo estamos generando una array y
almacenando sus valores. En el código inicial con el iterador, ningún
array es creado, todo el proceso de dividir la cadena y convertir a
entero se produce al recorrer el iterador cuando lo imprimimos
posteriormente, descartando todos esos valores intermedios una vez
consumidos.
El método parse es muy potente y nos permite convertir o
interpretar muchos tipos e datos. En realidad, nos permite convertir
desde cadena a cualquier tipo en Rust que implemente el método
FromStr para llevar a cabo esa conversión. Tipos como
float, bool o incluso IpAddr
pueden ser convertiso usando parse
Podemos pensar en los iteradores como una serie de operaciones que preparamos para que se ejecuten sobre unos ciertos valores de entrada. Esto se conoce como
stream processingy se ha popularizado en varios lenguajes recientemente tras la incorporación de elementos de programación funcional como las funciones lambda en los lenguajes. Podemos verlos en Python o Java.Dependiendo de lo que queramos hacer con los datos los iteradores pueden ser más interesantes ahorrando memoria, por ejemplo. La gran ventaja que ofrecen es el encadenamiento de operaciones así como una serie de formas de procesar los datos con las que conseguir resultados de forma sencilla y rápida. La siguiente línea de código calcularía la suma de todos los valores introducidos por el usuario, sin almacenarlos en memoria (solo cada uno de los valores temporalmemnte)
let sum: i32 = input.split_whitespace().map(str::parse::<i32>).filter_map(Result::ok).sum();
Si necesitamos determinar si las entradas del usuario son correctas
podemos evaluar Result con match, algo como
esto.
let mut numbers = Vec::new();
for num in input1.split_whitespaces() {
match num.parse::<i32>() {
Ok(n) => numbers.push(n),
Err(_) => {
println!("'{}' is not a valid integer.", part);
return;
}
}
}Si bien este código creo que es más legible, la forma idiomática de hacerlo en Rust (la recomendada digamos sería la siguiente:
let numbers: Result<Vec<i32>, _> = input1
.split_whitespace()
.map(|s| s.parse::<i32>())
.collect();
match numbers {
Ok(nums) => {
println!("Valid input: {:?}", nums);
}
Err(_) => {
println!("Please enter only integers.");
}
}Aquí estamos creando un objeto Result donde el valor
OK es nuestro vector. El elemento clave es la llamada a
collect() que genera precisamente eso. Si todos los valores
son correctos (OK) los recolecta en un vector que
estará disponible bajo el valor OK del Result
resultante (valga la redundancia). Si se produce cualquier error,
collect termina y retorna ese error con el valor
Err de Result. Observad que si bien, esto es
muy potente y elegante, no nos permite saber que valor fue el que
produjo el error, solo si todos los datos son correctos o hay algún
error.
Resumen
- En Rust tenemos que leer cadenas de caracteres de la consola y luego trocearlas o interpretarlas adecuiadamente según nuestras necesidades.
- El método
split_whitespace()nos permite dividir cadenas de caracteres fácilmente - El método
parse::<TYPE>nos permite interpretar cualquier tipo que implemente el métodoFromStr
■
