Colecciones y cadenas de Rust

Las colecciones son la forma más común de almacenar datos en las estructuras de datos. La biblioteca estándar de Rust ofrece una amplia variedad de tipos de colecciones para ayudar a los desarrolladores a manejar las operaciones de estructuras de datos.

Vector

Un vector es una estructura de datos que almacena múltiples valores, colocando linealmente en memoria los valores del mismo tipo.

El vector es una lista lineal, representada en Rust como Vec<T>.

La forma de usar un vector es similar a la de una lista (List). Podemos crear un vector del tipo especificado de esta manera:

let vector: Vec<i32> = Vec::new(); // 创建类型为 i32 的空向量
let vector = vec![1, 2, 4, 8];     // 通过数组创建向量

Al usar listas lineales, a menudo necesitamos operaciones de agregar, pero agregar es esencialmente lo mismo que la operación push de una pila, por lo que el vector solo tiene el método push para añadir un solo elemento:

, el vector devolverá un error.

fn main() {
    let mut vector = vec![1, 2, 4, 8];
    vector.push(16);
    vector.push(32);
    vector.push(64);
    println!("{:?}", vector);
}

Resultado de ejecución:

[1, 2, 4, 8, 16, 32, 64]

El método append se utiliza para concatenar un vector al final de otro:

Ejemplo

fn main() {
    let mut v1: Vec<i32> = vec![1, 2, 4, 8];
    let mut v2: Vec<i32> = vec![16, 32, 64];
    v1.append(&mut v2);
    println!("{:?}", v1);
}

Resultado de ejecución:

[1, 2, 4, 8, 16, 32, 64]

El método get se utiliza para obtener el valor de un vector:

Ejemplo

fn main() {
    let mut v = vec![1, 2, 4, 8];
    println!("{}", match v.get(0) {
        Some(value) => value.to_string(),
        None => "None".to_string()
    });
}

Resultado de ejecución:

1

Como la longitud del vector no se puede inferir lógicamente, el método get no puede garantizar la obtención de un valor, por lo que su valor de retorno es el tipo enumerado Option, que puede estar vacío.

Este es un método seguro para obtener valores, pero su escritura es un poco engorrosa. Si puedes garantizar que el índice no exceda el rango de índices del vector, también puedes usar la sintaxis de acceso a matrices:

Ejemplo

fn main() {
    let v = vec![1, 2, 4, 8];
    println!("{}", v[1]);
}

Resultado de ejecución:

2

Pero si intentamos obtener v

Iterar sobre un vector:

Ejemplo

fn main() {
    let v = vec![100, 32, 57];
    for i in &v {
            println!("{}", i);
    }
}

Resultado de ejecución:

100
32
57

Si necesitas cambiar el valor de la variable durante la iteración:

Ejemplo

fn main() {
    let mut v = vec![100, 32, 57];
    for i in &mut v {
        *i += 50;
    }
}

Cadena

El tipo String se ha utilizado mucho hasta este capítulo, por lo que muchos métodos ya son conocidos por el lector. Este capítulo se centra principalmente en los métodos de cadena y las propiedades de UTF-8.

Crear una cadena:

let string = String::new();

Convertir tipos básicos a cadena:

let one = 1.to_string();         // 整数到字符串
let float = 1.3.to_string();     // 浮点数到字符串
let slice = "slice".to_string(); // 字符串切片到字符串

Cadena que contiene caracteres UTF-8:

let hello = String::from("السلام عليكم");
let hello = String::from("Dobrý den");
let hello = String::from("Hello");
let hello = String::from("שָׁלוֹם");
let hello = String::from("नमस्ते");
let hello = String::from("こんにちは");
let hello = String::from("안녕하세요");
let hello = String::from("你好");
let hello = String::from("Olá");
let hello = String::from("Здравствуйте");
let hello = String::from("Hola");

Añadir a la cadena:

let mut s = String::from("run");
s.push_str("oob"); // 追加字符串切片
s.push('!');       // 追加字符

Concatenar cadenas con el signo +:

let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2;

Esta sintaxis también puede incluir slices de cadena:

let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");

let s = s1 + "-" + &s2 + "-" + &s3;

Usar la macro format!:

let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");

let s = format!("{}-{}-{}", s1, s2, s3);

Longitud de la cadena:

let s = "hello";
let len = s.len();

Aquí el valor de len es 5.

let s = "你好";
let len = s.len();

Aquí el valor de len es 6. Debido a que el chino está codificado en UTF-8, cada carácter ocupa 3 bytes, por lo que la longitud es 6. Sin embargo, Rust admite objetos de carácter UTF-8, por lo que si se quiere contar el número de caracteres, primero se puede convertir la cadena en una colección de caracteres:

let s = "hello你好";
let len = s.chars().count();

Aquí el valor de len es 7, porque hay 7 caracteres en total. Contar caracteres es mucho más lento que obtener la longitud de la cadena.

Iterar sobre una cadena:

Ejemplo

fn main() {
    let s = String::from(hello chino);
    for c in s.chars() {
        println!("{}", c);
    }
}

Resultado de ejecución:

h
e
l
l
o
中
文

Obtener un solo carácter de la cadena:

Ejemplo

fn main() {
    let s = String::from(EN chino);
    let a = s.chars().nth(2);
    println!("{:?}", a);
}

Resultado de ejecución:

Some('中')

Nota: La función nth es un método para obtener un valor de un iterador. ¡No la uses así durante la iteración! Porque en UTF-8 la longitud de cada carácter no es necesariamente igual.

Si quieres extraer una subcadena de la cadena:

Ejemplo

fn main() {
    let s = String::from(EN chino);
    let sub = &s[0..2];
    println!("{}", sub);
}

Resultado de ejecución:

EN

Pero ten en cuenta que este uso puede descomponer un carácter UTF-8. ¡Eso provocará un error:

Ejemplo

fn main() {
    let s = String::from(EN chino);
    let sub = &s[0..3];
    println!("{}", sub);
}

Resultado de ejecución:

thread 'main' panicked at 'byte index 3 is not a char boundary; it is inside '中' (bytes 2..5) of `EN中文`', src\libcore\str\mod.rs:2069:5 
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

Mapa

El mapa (Map) existe ampliamente en otros lenguajes. El más utilizado es el mapa hash de pares clave-valor.

Crear un mapa hash:

Ejemplo

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();

    map.insert("color", "red");
    map.insert("size", "10 m^2");

    println!("{}", map.get("color").unwrap());
}

Nota: Aquí no se declaran los genéricos del mapa hash, debido al mecanismo de inferencia automática de tipos de Rust.

Resultado de ejecución:

red

Los métodos insert y get son los dos métodos más utilizados en el mapa.

El mapa admite iteradores:

Ejemplo

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();

    map.insert("color", "red");
    map.insert("size", "10 m^2");

    for p in map.iter() {
        println!("{:?}", p);
    }
}

Resultado de ejecución:

("color", "red") 
("size", "10 m^2")

Los elementos iterados son tuplas que representan pares clave-valor.

El mapa de Rust es una estructura de datos muy conveniente. Cuando se usa el método insert para añadir un nuevo par clave-valor, si ya existe la misma clave, se sobrescribe directamente el valor correspondiente. Si deseas una "inserción segura", es decir, realizar la inserción solo cuando se confirma que la clave no existe actualmente, puedes hacerlo así:

map.entry("color").or_insert("red");

Esto significa que si no hay un par clave-valor con la clave "color", se añade y se establece el valor "red"; de lo contrario, se omite.

Si ya se sabe que una clave existe y se quiere modificar directamente su valor, hay una forma más rápida:

Ejemplo

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(1, "a");
   
    if let Some(x) = map.get_mut(&1) {
        *x = "b";
    }
}
Otras extensiones