This commit is contained in:
Rorik Star Platinum 2025-11-27 23:37:40 +03:00
parent f2b07c9f30
commit 17f37947f3
8 changed files with 485 additions and 16 deletions

View file

@ -0,0 +1,85 @@
В функциональном стиле, когда тебе нужно свести последовательность значений к одному числу (произведению в данном случае), используют **свертку** (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 нормально сочетать оба подхода.