hyperion/20-dev/00-rust/rustlings/Untitled.md
2025-11-27 23:37:40 +03:00

85 lines
No EOL
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

В функциональном стиле, когда тебе нужно свести последовательность значений к одному числу (произведению в данном случае), используют **свертку** (fold) или, в более простых случаях, готовые методы агрегации вроде `product`.
В Rust у итераторов есть метод `product()`, который делает ровно то, что нужно для факториала.
### Решение 1: Самое идиоматичное (Iterators + product)
Мы создаем диапазон от `1` до `num` (включительно) и перемножаем все числа.
```rust
fn factorial(num: u64) -> u64 {
(1..=num).product()
}
```
**Почему это работает:**
1. `(1..=num)` создает итератор, генерирующий числа `1, 2, 3, ..., num`.
* Если `num` равно `0`, диапазон `1..=0` пуст.
2. `.product()` проходит по итератору, умножая аккумулятор на каждый элемент.
* Для пустого итератора (факториал 0) `product()` возвращает мультипликативную единицу (`1`), что математически верно: $0! = 1$.
***
### Решение 2: Более явное (Fold)
Если бы метода `product()` не существовало, мы бы использовали `fold`. Это классическая "reduce" операция из функционального программирования.
```rust
fn factorial(num: u64) -> u64 {
(1..=num).fold(1, |acc, x| acc * x)
}
```
**Разбор:**
* `1` (первый аргумент) — начальное значение аккумулятора.
* `|acc, x| acc * x` — замыкание, которое берет текущий аккумулятор (`acc`) и следующий элемент (`x`), и возвращает новое значение аккумулятора.
* Для `num = 4`:
1. `acc = 1` (начальное), `x = 1` -> `1 * 1 = 1`
2. `acc = 1`, `x = 2` -> `1 * 2 = 2`
3. `acc = 2`, `x = 3` -> `2 * 3 = 6`
4. `acc = 6`, `x = 4` -> `6 * 4 = 24`
**Итог:**
Используй `(1..=num).product()` — это самый "ржавый" (Rustacean) и читаемый способ. Он выполняет все твои условия:
* Нет явного `return` (это выражение).
* Нет циклов `for/while` (спрятаны внутри итератора).
* Нет мутабельных переменных.
* Нет рекурсии.
### 1. "Джентльменский набор" итераторов
Помимо уже знакомых тебе `map`, `filter`, `fold` и `flat_map`, вот что ты будешь использовать в 90% случаев:
* **`collect()`** — ты уже знаешь, это "материализатор". Превращает поток обратно в коллекцию (`Vec`, `HashMap`, `String`...).
* **`find(|x| ...)`** — ищет **первый** элемент, удовлетворяющий условию. Возвращает `Option<T>`.
* **`any(|x| ...)` / `all(|x| ...)`** — возвращают `bool`. `any` проверяет, есть ли *хотя бы один*, `all` — что *все* соответствуют.
* **`enumerate()`** — добавляет индекс к элементу. Вместо `x` получаешь `(index, x)`.
```rust
// Вывести строки с номерами
lines.iter().enumerate().for_each(|(i, line)| println!("{}: {}", i, line));
```
* **`zip(other_iter)`** — "сшивает" два итератора в один поток пар `(a, b)`.
* **`take(n)` / `skip(n)`** — берет первые `n` или пропускает первые `n` элементов.
* **`chain(other_iter)`** — склеивает два итератора: "сначала всё из первого, потом всё из второго".
* **`inspect(|x| ...)`** — бесценно для отладки! Позволяет "подсмотреть" значение в середине цепочки, не меняя его (обычно там делают `println!`).
### 2. Вся ли функциональщина в Rust — это итераторы?
И да, и нет.
* **Да**, итераторы — это главный локомотив функционального стиля в Rust. Это самый мощный и оптимизированный инструмент для обработки данных.
* **Нет**, ФП в Rust шире. Оно включает:
* **Enum + Pattern Matching** (алгебраические типы данных) — это основа логики, замена классам и полиморфизму.
* **Immutability** (неизменяемость по умолчанию).
* **Closures** (замыкания) — функции высшего порядка, которые можно передавать и возвращать.
Но именно цепочки итераторов делают код "визуально функциональным".
### 3. Каждую ли задачу можно решить функционально?
**Теоретически — да.** Любой цикл можно переписать через рекурсию или `fold`.
**Практически — не стоит.**
Rust — прагматичный язык. Иногда **императивный код лучше**:
1. **Сложная мутация состояния**: Если тебе нужно обновлять 5 разных переменных в зависимости от сложной логики на каждом шаге, `fold` превратится в ад с кортежем из 5 элементов. Обычный `for` будет чище.
2. **Ранний выход из вложенных циклов**: Выйти из тройного вложенного цикла через `break 'label` проще, чем писать цепочку итераторов, которая умеет прерываться.
3. **Графы и произвольный доступ**: Итераторы хороши для *последовательностей*. Если ты прыгаешь по индексам массива туда-сюда (`i`, `i+5`, `i/2`), функциональный стиль будет выглядеть как натягивание совы на глобус.
**Совет:** Используй итераторы для *трансформации потоков данных* (фильтрация, маппинг, поиск, агрегация). Используй циклы для *сложного управления потоком выполнения* или когда состояние слишком запутанное. В Rust нормально сочетать оба подхода.