ndarray для Rust: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

ndarray для Rust: полное руководство по работе с N-мерными массивами

1. Что это и зачем нужна

Библиотека ndarray — это стандартное расширение экосистемы Rust для работы с многомерными массивами (N-мерными тензорами). Она предоставляет мощные, типобезопасные и производительные структуры данных для численных вычислений, линейной алгебры, обработки сигналов, машинного обучения и научных расчётов. Если вы знакомы с NumPy в Python, то ndarray — это его прямой аналог в мире Rust, но с преимуществами строгой типизации, отсутствия сборщика мусора и контроля над памятью.

Зачем она нужна? В Rust нет встроенной поддержки многомерных массивов с произвольным количеством осей. Стандартные векторы (Vec<T>) и срезы (&[T]) позволяют работать только с одномерными данными. Для задач, где требуется матричная алгебра, свёртки, тензорные операции или работа с изображениями, необходима специализированная библиотека. ndarray решает эти проблемы, предоставляя:

  • Типизированные N-мерные массивы (ArrayBase) с фиксированным или динамическим числом осей.
  • Эффективные срезы (slicing), индексацию и итерацию.
  • Векторизованные арифметические операции (broadcasting).
  • Интеграцию с BLAS/LAPACK через ndarray-linalg для высокопроизводительных линейно-алгебраических вычислений.
  • Поддержку параллельных вычислений через rayon.

Библиотека активно используется в таких проектах, как RustML, Linfa (фреймворк машинного обучения), Image (обработка изображений) и многих других. Она является фундаментом для научных вычислений в Rust.

2. Установка

Для установки ndarray используйте менеджер пакетов Cargo, входящий в состав Rust. Добавьте зависимость в файл Cargo.toml вашего проекта:

[dependencies]
ndarray = "0.16"

Затем выполните команду в терминале:

cargo build

Для работы с линейной алгеброй (матричные умножения, собственные значения) добавьте ndarray-linalg и выберите бэкенд BLAS (например, OpenBLAS или Intel MKL):

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

Для параллельных итераций используйте ndarray-parallel:

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

3. Быстрый старт — минимальный рабочий пример

Создадим одномерный массив, выполним поэлементное сложение и выведем результат. Сохраните следующий код в файл src/main.rs:

use ndarray::Array;

fn main() {
    // Создаём одномерный массив из 5 элементов
    let a = Array::from_vec(vec![1, 2, 3, 4, 5]);
    let b = Array::from_vec(vec![10, 20, 30, 40, 50]);

    // Поэлементное сложение
    let c = &a + &b;

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

Запустите программу:

cargo run

Вывод:

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. Основные методы и функции (минимум 8)

4.1. Array::from_shape_vec

Сигнатура: pub fn from_shape_vec<Sh>(shape: Sh, v: Vec<A>) -> Result<ArrayBase<OwnedRepr<A>, Dim<Sh::Repr>>, ShapeError>

Что делает: Создаёт N-мерный массив из одномерного вектора, интерпретируя его как данные, уложенные в порядке C (row-major). Возвращает Result, так как размер вектора должен совпадать с произведением размерностей.

use ndarray::Array;

fn main() {
    // Создаём матрицу 2x3 из вектора
    let a = Array::from_shape_vec((2, 3), vec![1, 2, 3, 4, 5, 6]).unwrap();
    println!("{:?}", a);
    // Вывод: [[1, 2, 3],
    //         [4, 5, 6]]
}

4.2. .shape()

Сигнатура: pub fn shape(&self) -> &Self::Dim

Что делает: Возвращает ссылку на кортеж, представляющий размерности массива по каждой оси.

use ndarray::Array;

fn main() {
    let a = Array::range(0., 12., 1.).into_shape((3, 4)).unwrap();
    println!("Shape: {:?}", a.shape()); // Shape: [3, 4]
}

4.3. .sum()

Сигнатура: pub fn sum(&self) -> A (где A — тип элемента, реализующий Add и Zero)

Что делает: Вычисляет сумму всех элементов массива.

use ndarray::Array;

fn main() {
    let a = Array::from_vec(vec![1, 2, 3, 4, 5]);
    let total = a.sum();
    println!("Sum: {}", total); // Sum: 15
}

4.4. .mapv()

Сигнатура: pub fn mapv<B, F>(&self, f: F) -> ArrayBase<OwnedRepr<B>, Dim<Ix1>> (для 1D) или обобщённо для N-мерных: pub fn mapv_into<B, F>(self, f: F) -> ArrayBase<OwnedRepr<B>, Self::Dim>

Что делает: Применяет функцию f к каждому элементу массива и возвращает новый массив с результатами.

use ndarray::Array;

fn main() {
    let a = Array::from_vec(vec![1.0, 2.0, 3.0]);
    let b = a.mapv(|x| x * 2.0 + 1.0);
    println!("{:?}", b); // [3, 5, 7]
}

4.5. .dot() (скалярное произведение)

Сигнатура: pub fn dot<S2>(&self, rhs: &ArrayBase<S2, Ix1>) -> A (для 1D) или pub fn dot<S2>(&self, rhs: &ArrayBase<S2, Ix2>) -> ArrayBase<OwnedRepr<A>, Ix2> (для 2D — матричное умножение)

Что делает: Вычисляет скалярное произведение двух одномерных массивов или матричное произведение двух двумерных массивов.

use ndarray::Array;

fn main() {
    let a = Array::from_vec(vec![1, 2, 3]);
    let b = Array::from_vec(vec![4, 5, 6]);
    let dot_product = a.dot(&b);
    println!("Dot product: {}", dot_product); // 1*4 + 2*5 + 3*6 = 32

    // Матричное умножение
    let m1 = Array::from_shape_vec((2, 3), vec![1, 2, 3, 4, 5, 6]).unwrap();
    let m2 = Array::from_shape_vec((3, 2), vec![7, 8, 9, 10, 11, 12]).unwrap();
    let result = m1.dot(&m2);
    println!("Matrix product:\n{:?}", result);
    // [[58, 64],
    //  [139, 154]]
}

4.6. .slice()

Сигнатура: pub fn slice<I>(&self, info: I) -> ArrayView<A, I::OutDim> где I — кортеж срезов Si или s![...]

Что делает: Создаёт представление (view) на подмассив без копирования данных. Поддерживает шаги и диапазоны.

use ndarray::{Array, s};

fn main() {
    let a = Array::range(0., 16., 1.).into_shape((4, 4)).unwrap();
    // Берём подматрицу: строки 1..3, столбцы 1..3
    let view = a.slice(s![1..3, 1..3]);
    println!("{:?}", view);
    // [[5, 6],
    //  [9, 10]]
}

4.7. .fold()

Сигнатура: pub fn fold<B, F>(&self, init: B, f: F) -> B

Что делает: Свёртка (редукция) массива с заданным начальным значением и бинарной операцией. Порядок обхода — row-major.

use ndarray::Array;

fn main() {
    let a = Array::from_vec(vec![1, 2, 3, 4]);
    // Вычисляем произведение всех элементов
    let product = a.fold(1, |acc, &x| acc * x);
    println!("Product: {}", product); // 24
}

4.8. Array::eye()

Сигнатура: pub fn eye(n: usize) -> Array2<A> (где A — тип с плавающей точкой или целый, реализующий One и Zero)

Что делает: Создаёт единичную матрицу размером n x n.

use ndarray::Array;

fn main() {
    let identity = Array::eye::<f64>(3);
    println!("{:?}", identity);
    // [[1, 0, 0],
    //  [0, 1, 0],
    //  [0, 0, 1]]
}

4.9. .t() (транспонирование)

Сигнатура: pub fn t(&self) -> ArrayView<A, Ix2>

Что делает: Возвращает транспонированное представление двумерного массива (строки становятся столбцами). Не копирует данные.

use ndarray::Array;

fn main() {
    let a = Array::from_shape_vec((2, 3), vec![1, 2, 3, 4, 5, 6]).unwrap();
    let b = a.t();
    println!("Original:\n{:?}\nTransposed:\n{:?}", a, b);
    // Original:
    // [[1, 2, 3],
    //  [4, 5, 6]]
    // Transposed:
    // [[1, 4],
    //  [2, 5],
    //  [3, 6]]
}

4.10. Array::from_iter()

Сигнатура: pub fn from_iter<I>(iterable: I) -> ArrayBase<OwnedRepr<A>, Ix1>

Что делает: Создаёт одномерный массив из итератора.

use ndarray::Array;

fn main() {
    let squares: Array<i32, _> = (0..5).map(|x| x * x).collect();
    println!("{:?}", squares); // [0, 1, 4, 9, 16]
}

5. Реальный практический пример (100-200 строк кода)

Рассмотрим задачу: реализовать нормализацию изображения (матрицы пикселей) методом Min-Max scaling и применить свёртку (blur) с помощью ядра 3x3. Пример демонстрирует работу с 2D массивами, срезами, broadcasting и поэлементными операциями.

use ndarray::{Array, Array2, s};
use std::f64;

/// Нормализация Min-Max: приводит значения к диапазону [0, 1]
fn                    

Рекомендаций