ndarray en Rust: guía completa con ejemplos prácticos

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

ndarray en Rust: guía completa para trabajar con arreglos N-dimensionales

1. ¿Qué es ndarray y por qué usarla?

La librería ndarray es la extensión estándar del ecosistema Rust para trabajar con arreglos multidimensionales (tensores N-dimensionales). Proporciona estructuras de datos potentes, seguras en cuanto a tipos y de alto rendimiento para cálculos numéricos, álgebra lineal, procesamiento de señales, aprendizaje automático y cálculos científicos. Si conoces NumPy en Python, ndarray es su equivalente directo en el mundo Rust, pero con las ventajas de un tipado estricto, ausencia de recolector de basura y control total sobre la memoria.

¿Por qué es necesaria? Rust no tiene soporte nativo para arreglos multidimensionales con un número arbitrario de ejes. Los vectores estándar (Vec<T>) y los slices (&[T]) solo permiten trabajar con datos unidimensionales. Para tareas que requieren álgebra matricial, convoluciones, operaciones con tensores o procesamiento de imágenes, se necesita una librería especializada. ndarray resuelve estos problemas ofreciendo:

  • Arreglos N-dimensionales tipados (ArrayBase) con un número fijo o dinámico de ejes.
  • Slicing eficiente, indexación e iteración.
  • Operaciones aritméticas vectorizadas (broadcasting).
  • Integración con BLAS/LAPACK a través de ndarray-linalg para cálculos de álgebra lineal de alto rendimiento.
  • Soporte para cálculos paralelos mediante rayon.

La librería se utiliza activamente en proyectos como RustML, Linfa (framework de aprendizaje automático), Image (procesamiento de imágenes) y muchos otros. Es la base de la computación científica en Rust.

2. Instalación

Para instalar ndarray, usa el gestor de paquetes Cargo, que viene incluido con Rust. Añade la dependencia en tu archivo Cargo.toml:

[dependencies]
ndarray = "0.16"

Luego, ejecuta el siguiente comando en la terminal:

cargo build

Para trabajar con álgebra lineal (multiplicación de matrices, valores propios), añade ndarray-linalg y elige un backend BLAS (por ejemplo, OpenBLAS o Intel MKL):

[dependencies]
ndarray = "0.16"
ndarray-linalg = { version = "0.16", features = ["openblas"] }

Para iteraciones paralelas, usa ndarray-parallel:

[dependencies]
ndarray = "0.16"
ndarray-parallel = "0.16"

3. Inicio rápido — ejemplo mínimo funcional

Creemos un arreglo unidimensional, realicemos una suma elemento a elemento y mostremos el resultado. Guarda el siguiente código en el archivo src/main.rs:

use ndarray::Array;

fn main() {
    // Creamos un arreglo unidimensional de 5 elementos
    let a = Array::from_vec(vec![1, 2, 3, 4, 5]);
    let b = Array::from_vec(vec![10, 20, 30, 40, 50]);

    // Suma elemento a elemento
    let c = &a + &b;

    println!("a = {:?}", a);
    println!("b = {:?}", b);
    println!("a + b = {:?}", c);
}

Ejecuta el programa:

cargo run

Salida:

a = [1, 2, 3, 4, 5], shape=[5], strides=[1], layout=CFcf (0x1)
b = [10, 20, 30, 40, 50], shape=[5], strides=[1], layout=CFcf (0x1)
a + b = [11, 22, 33, 44, 55], shape=[5], strides=[1], layout=CFcf (0x1)

4. Métodos y funciones principales (mínimo 8)

4.1. Array::from_shape_vec

Firma: pub fn from_shape_vec<Sh>(shape: Sh, v: Vec<A>) -> Result<ArrayBase<OwnedRepr<A>, Dim<Sh>>, ShapeError>

Crea un arreglo a partir de un vector y una forma (shape) específica. Es útil cuando ya tienes los datos en un vector y deseas darles una estructura multidimensional.

use ndarray::Array;

let data = vec![1, 2, 3, 4, 5, 6];
let arr = Array::from_shape_vec((2, 3), data).unwrap();
println!("{:?}", arr);
// Salida: [[1, 2, 3],
//          [4, 5, 6]], shape=[2, 3], strides=[3, 1]

4.2. .shape()

Firma: pub fn shape(&self) -> &IxS

Devuelve las dimensiones del arreglo como un slice. Esencial para conocer la estructura del arreglo en tiempo de ejecución.

let arr = Array::from_shape_vec((2, 3), vec![1, 2, 3, 4, 5, 6]).unwrap();
println!("Shape: {:?}", arr.shape()); // Salida: [2, 3]

4.3. .len()

Firma: pub fn len(&self) -> usize

Devuelve el número total de elementos en el arreglo.

let arr = Array::from_shape_vec((2, 3), vec![1, 2, 3, 4, 5, 6]).unwrap();
println!("Total de elementos: {}", arr.len()); // Salida: 6

4.4. .sum()

Firma: pub fn sum(&self) -> A

Calcula la suma de todos los elementos del arreglo. Muy útil para agregaciones rápidas.

let arr = Array::from_vec(vec![1, 2, 3, 4, 5]);
println!("Suma: {}", arr.sum()); // Salida: 15

4.5. .mean()

Firma: pub fn mean(&self) -> A

Calcula la media aritmética de todos los elementos. Requiere que el tipo de dato implemente el trait Float.

use ndarray::Array1;

let arr = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
println!("Media: {}", arr.mean().unwrap()); // Salida: 3.0

4.6. .dot()

Firma: pub fn dot(&self, rhs: &ArrayBase<S, Ix1>) -> A

Calcula el producto punto entre dos arreglos unidimensionales. Es la base de muchas operaciones de álgebra lineal.

use ndarray::Array1;

let a = Array1::from_vec(vec![1, 2, 3]);
let b = Array1::from_vec(vec![4, 5, 6]);
let producto = a.dot(&b);
println!("Producto punto: {}", producto); // Salida: 32

4.7. .t()

Firma: pub fn t(&self) -> ArrayBase<ViewRepr<&A>, Ix2>

Devuelve la transpuesta de un arreglo bidimensional (matriz). No modifica el arreglo original, sino que crea una vista transpuesta.

use ndarray::Array;

let arr = Array::from_shape_vec((2, 3), vec![1, 2, 3, 4, 5, 6]).unwrap();
let transpuesta = arr.t().to_owned();
println!("Original:\n{:?}", arr);
println!("Transpuesta:\n{:?}", transpuesta);
// Original: [[1, 2, 3],
//            [4, 5, 6]]
// Transpuesta: [[1, 4],
//               [2, 5],
//               [3, 6]]

4.8. .map()

Firma: pub fn map<B, F>(&self, f: F) -> ArrayBase<OwnedRepr<B>, D> where F: FnMut(&A) -> B

Aplica una función a cada elemento del arreglo, devolviendo un nuevo arreglo con los resultados. Ideal para transformaciones elemento a elemento.

use ndarray::Array;

let arr = Array::from_vec(vec![1, 2, 3, 4, 5]);
let cuadrados = arr.map(|x| x * x);
println!("Cuadrados: {:?}", cuadrados); // Salida: [1, 4, 9, 16, 25]

4.9. .slice()

Firma: pub fn slice<I>(&self, info: I) -> ArrayBase<ViewRepr<&A>, I::Out> where I: SliceInfo<...>

Extrae una vista (sin copiar datos) de una porción del arreglo usando la sintaxis s![...]. Es fundamental para acceder a subarreglos de manera eficiente.

use ndarray::{Array, s};

let arr = Array::from_shape_vec((3, 4), (0..12).collect()).unwrap();
let sub_arr = arr.slice(s![1..3, 0..2]);
println!("Subarreglo:\n{:?}", sub_arr);
// Salida: [[4, 5],
//          [8, 9]]

4.10. Array::zeros

Firma: pub fn zeros<Sh, A>(shape: Sh) -> ArrayBase<OwnedRepr<A>, Dim<Sh>> where A: Clone + Zero

Crea un arreglo lleno de ceros con la forma especificada. Muy útil para inicializar arreglos antes de llenarlos con datos.

use ndarray::Array;

let zeros = Array::zeros((3, 3));
println!("Arreglo de ceros:\n{:?}", zeros);
// Salida: [[0, 0, 0],
//          [0, 0, 0],
//          [0, 0, 0]]

Recommendations