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