Понимание рекурсии

Исследуйте прекрасный мир рекурсии в Топазе. Полное руководство от базовой рекурсии до продвинутых техник, оптимизации и практических применений.

Рекурсия — один из самых красивых и мощных концептов в программировании. Откройте для себя элегантность рекурсии в Топазе! 🌀

🌱 Основы рекурсии

Что такое рекурсия?

Рекурсия — это техника программирования, при которой функция вызывает саму себя. Она решает сложные задачи, разбивая их на более мелкие идентичные подзадачи.

// Простейшая рекурсия - факториал
function факториал(число: int) -> int {
    match число {
        case 0 | 1 => 1                              // Базовый случай
        case _ => число * факториал(число - 1)        // Рекурсивный вызов
    }
} test {
    assert факториал(0) == 1
    assert факториал(1) == 1
    assert факториал(5) == 120
    assert факториал(10) == 3628800
}

// Последовательность Фибоначчи
function фибоначчи(число: int) -> int {
    match число {
        case 0 => 0
        case 1 => 1
        case _ => фибоначчи(число - 1) + фибоначчи(число - 2)
    }
} test {
    assert фибоначчи(0) == 0
    assert фибоначчи(1) == 1
    assert фибоначчи(8) == 21
    assert фибоначчи(12) == 144
}

print("факториал(5) = {факториал(5)}")
print("фибоначчи(8) = {фибоначчи(8)}")

Основные элементы рекурсии

  1. Базовый случай: Условие, останавливающее рекурсию
  2. Рекурсивное отношение: Часть, которая вызывает сама себя
  3. Сходимость: Должна в конечном итоге достичь базового случая

🎨 Различные паттерны рекурсии

Линейная рекурсия

// Сумма элементов массива
function суммаМассива(массив: Array<int>, индекс: int = 0) -> int {
    if индекс >= массив.length {
        return 0
    }
    return массив[индекс] + суммаМассива(массив, индекс + 1)
}

// Обращение строки
function обратитьСтроку(строка: string) -> string {
    if строка.length <= 1 {
        return строка
    }
    
    let первыйСимвол = строка.charAt(0)
    let остаток = строка.substring(1)
    
    return обратитьСтроку(остаток) + первыйСимвол
}

// Наибольший общий делитель (алгоритм Евклида)
function НОД(а: int, б: int) -> int {
    match б {
        case 0 => а
        case _ => НОД(б, а % б)
    }
} test {
    let числа = [1, 2, 3, 4, 5]
    assert суммаМассива(числа) == 15
    
    assert обратитьСтроку("привет") == "тевирп"
    assert обратитьСтроку("Топаз") == "zapoT"
    
    assert НОД(48, 18) == 6
    assert НОД(100, 25) == 25
}

print("Сумма массива: {суммаМассива([1, 2, 3, 4, 5])}")
print("Обращение строки: {обратитьСтроку("Привет Мир")}")
print("НОД(48, 18): {НОД(48, 18)}")

Разделяй и властвуй

// Бинарный поиск
function бинарныйПоиск<T>(
    массив: Array<T>,
    цель: T,
    начало: int = 0,
    конец: int = -1
) -> Option<int>
where
    T: PartialOrd + PartialEq
{
    let реальныйКонец = if конец == -1 { массив.length - 1 } else { конец }
    
    if начало > реальныйКонец {
        return None
    }
    
    let середина = (начало + реальныйКонец) / 2
    
    match массив[середина] {
        case x if x == цель => Some(середина)
        case x if x > цель => бинарныйПоиск(массив, цель, начало, середина - 1)
        case _ => бинарныйПоиск(массив, цель, середина + 1, реальныйКонец)
    }
}

// Сортировка слиянием
function сортировкаСлиянием(массив: Array<int>) -> Array<int> {
    if массив.length <= 1 {
        return массив
    }
    
    let середина = массив.length / 2
    let левая = массив[0..середина].to_vec()
    let правая = массив[середина..].to_vec()
    
    let отсортированнаяЛевая = сортировкаСлиянием(левая)
    let отсортированнаяПравая = сортировкаСлиянием(правая)
    
    return слить(отсортированнаяЛевая, отсортированнаяПравая)
}

function слить(левая: Array<int>, правая: Array<int>) -> Array<int> {
    let mut результат: Array<int> = []
    let mut и = 0
    let mut д = 0
    
    while и < левая.length && д < правая.length {
        if левая[и] <= правая[д] {
            результат.push(левая[и])
            и += 1
        } else {
            результат.push(правая[д])
            д += 1
        }
    }
    
    // Добавить оставшиеся элементы
    результат.extend(&левая[и..])
    результат.extend(&правая[д..])
    
    return результат
}

// Быстрая сортировка
function быстраяСортировка(массив: Array<int>) -> Array<int> {
    if массив.length <= 1 {
        return массив
    }
    
    let опорный = массив[массив.length / 2]
    let меньшие = массив.filter(|&x| x < опорный)
    let равные = массив.filter(|&x| x == опорный)
    let большие = массив.filter(|&x| x > опорный)
    
    return [
        ...быстраяСортировка(меньшие),
        ...равные,
        ...быстраяСортировка(большие)
    ]
} test {
    let отсортированныйМассив = [1, 3, 5, 7, 9, 11, 13]
    assert бинарныйПоиск(отсортированныйМассив, 7) == Some(3)
    assert бинарныйПоиск(отсортированныйМассив, 14) == None
    
    let случайныйМассив = [64, 34, 25, 12, 22, 11, 90]
    let результатСортировки1 = сортировкаСлиянием(случайныйМассив.clone())
    let результатСортировки2 = быстраяСортировка(случайныйМассив.clone())
    
    assert результатСортировки1 == [11, 12, 22, 25, 34, 64, 90]
    assert результатСортировки2 == [11, 12, 22, 25, 34, 64, 90]
}

print("Результат бинарного поиска: {бинарныйПоиск([1, 3, 5, 7, 9], 5)}")
print("Сортировка слиянием: {сортировкаСлиянием([3, 1, 4, 1, 5, 9])}")
print("Быстрая сортировка: {быстраяСортировка([3, 1, 4, 1, 5, 9])}")

Древовидная рекурсия

// Структура бинарного дерева
enum БинарноеДерево<T> {
    Пустое,
    Узел {
        значение: T,
        левый: Box<БинарноеДерево<T>>,
        правый: Box<БинарноеДерево<T>>
    }
}

impl<T> БинарноеДерево<T>
where
    T: Clone + std::fmt::Display + PartialOrd
{
    function новыйУзел(значение: T) -> БинарноеДерево<T> {
        return БинарноеДерево::Узел {
            значение,
            левый: Box::new(БинарноеДерево::Пустое),
            правый: Box::new(БинарноеДерево::Пустое)
        }
    }
    
    // Вставить значение в дерево
    function вставить(&mut self, новоеЗначение: T) {
        match self {
            case БинарноеДерево::Пустое => {
                *self = БинарноеДерево::новыйУзел(новоеЗначение)
            }
            case БинарноеДерево::Узел { значение, левый, правый } => {
                if новоеЗначение <= *значение {
                    левый.вставить(новоеЗначение)
                } else {
                    правый.вставить(новоеЗначение)
                }
            }
        }
    }
    
    // Поиск в дереве
    function найти(&self, цель: &T) -> bool {
        match self {
            case БинарноеДерево::Пустое => false
            case БинарноеДерево::Узел { значение, левый, правый } => {
                if цель == значение {
                    return true
                } else if цель < значение {
                    return левый.найти(цель)
                } else {
                    return правый.найти(цель)
                }
            }
        }
    }
    
    // Симметричный обход
    function симметричныйОбход(&self) -> Array<T> {
        match self {
            case БинарноеДерево::Пустое => []
            case БинарноеДерево::Узел { значение, левый, правый } => {
                let mut результат = левый.симметричныйОбход()
                результат.push(значение.clone())
                результат.extend(правый.симметричныйОбход())
                return результат
            }
        }
    }
    
    // Вычислить высоту дерева
    function высота(&self) -> int {
        match self {
            case БинарноеДерево::Пустое => 0
            case БинарноеДерево::Узел { левый, правый, .. } => {
                let высотаЛевого = левый.высота()
                let высотаПравого = правый.высота()
                return 1 + std::cmp::max(высотаЛевого, высотаПравого)
            }
        }
    }
    
    // Подсчет узлов
    function количествоУзлов(&self) -> int {
        match self {
            case БинарноеДерево::Пустое => 0
            case БинарноеДерево::Узел { левый, правый, .. } => {
                return 1 + левый.количествоУзлов() + правый.количествоУзлов()
            }
        }
    }
} test {
    let mut дерево = БинарноеДерево::Пустое
    
    // Вставка значений
    let значения = [5, 3, 7, 1, 9, 4, 6]
    for значение in значения {
        дерево.вставить(значение)
    }
    
    // Тесты поиска
    assert дерево.найти(&5) == true
    assert дерево.найти(&10) == false
    
    // Симметричный обход (отсортированный порядок)
    let результатОбхода = дерево.симметричныйОбход()
    assert результатОбхода == [1, 3, 4, 5, 6, 7, 9]
    
    // Свойства дерева
    assert дерево.высота() == 4
    assert дерево.количествоУзлов() == 7
}

// Пример использования
let mut моёДерево = БинарноеДерево::Пустое
[15, 10, 20, 8, 12, 16, 25].iter().for_each(|&значение| моёДерево.вставить(значение))

print("Симметричный обход дерева: {моёДерево.симметричныйОбход()}")
print("Высота дерева: {моёДерево.высота()}")
print("Количество узлов: {моёДерево.количествоУзлов()}")
print("Поиск 15: {моёДерево.найти(&15)}")

⚡ Техники оптимизации рекурсии

Хвостовая рекурсия

// Сравнение обычной рекурсии и хвостовой рекурсии

// Обычная рекурсия - риск переполнения стека
function факториал_обычная(число: int) -> int {
    if число <= 1 {
        return 1
    }
    return число * факториал_обычная(число - 1)  // Умножение после рекурсивного вызова
}

// Хвостовая рекурсия - оптимизируемая
function факториал_хвостовая(число: int, аккумулятор: int = 1) -> int {
    if число <= 1 {
        return аккумулятор
    }
    return факториал_хвостовая(число - 1, аккумулятор * число)  // Рекурсивный вызов последняя операция
}

// Фибоначчи с хвостовой рекурсией
function фибоначчи_хвостовая(число: int, а: int = 0, б: int = 1) -> int {
    match число {
        case 0 => а
        case 1 => б
        case _ => фибоначчи_хвостовая(число - 1, б, а + б)
    }
}

// Обращение списка с хвостовой рекурсией
function обратить_хвостовая<T>(
    исходный: Array<T>,
    результат: Array<T> = [],
    индекс: int = 0
) -> Array<T> {
    if индекс >= исходный.length {
        return результат
    }
    
    let mut новыйРезультат = [исходный[индекс]].to_vec()
    новыйРезультат.extend(результат)
    
    return обратить_хвостовая(исходный, новыйРезультат, индекс + 1)
}

// Возведение в степень с хвостовой рекурсией
function степень_хвостовая(основание: int, показатель: int, аккумулятор: int = 1) -> int {
    match показатель {
        case 0 => аккумулятор
        case показ if показ % 2 == 0 => степень_хвостовая(основание * основание, показ / 2, аккумулятор)
        case показ => степень_хвостовая(основание, показ - 1, аккумулятор * основание)
    }
} test {
    // Тесты сравнения производительности
    assert факториал_обычная(10) == факториал_хвостовая(10)
    assert фибоначчи_хвостовая(10) == 55
    
    let исходныйСписок = [1, 2, 3, 4, 5]
    let обращённыйСписок = обратить_хвостовая(исходныйСписок)
    assert обращённыйСписок == [5, 4, 3, 2, 1]
    
    assert степень_хвостовая(2, 10) == 1024
    assert степень_хвостовая(3, 4) == 81
}

print("Хвостовая рекурсия факториал(10): {факториал_хвостовая(10)}")
print("Хвостовая рекурсия фибоначчи(15): {фибоначчи_хвостовая(15)}")
print("Степень 2^10: {степень_хвостовая(2, 10)}")

Мемоизация

use std::collections::HashMap

// Структура-обёртка для мемоизации
struct Мемоизация<K, V>
where
    K: Clone + std::hash::Hash + Eq,
    V: Clone
{
    кэш: HashMap<K, V>,
    функция: fn(K, &mut Мемоизация<K, V>) -> V
}

impl<K, V> Мемоизация<K, V>
where
    K: Clone + std::hash::Hash + Eq,
    V: Clone
{
    function новая(функция: fn(K, &mut Мемоизация<K, V>) -> V) -> Self {
        return Мемоизация {
            кэш: HashMap::new(),
            функция
        }
    }
    
    function вызвать(&mut self, аргумент: K) -> V {
        match self.кэш.get(&аргумент) { case Some(результат) => return результат.clone(), case None => {} }
        
        let результат = (self.функция)(аргумент.clone(), self)
        self.кэш.insert(аргумент, результат.clone())
        return результат
    }
}

// Мемоизированный фибоначчи
function фибоначчи_мемо_функция(число: int, мемо: &mut Мемоизация<int, int>) -> int {
    match число {
        case 0 => 0
        case 1 => 1
        case _ => мемо.вызвать(число - 1) + мемо.вызвать(число - 2)
    }
}

// Мемоизация вычисления сочетаний (nCr)
function сочетания_мемо_функция(
    параметры: (int, int),
    мемо: &mut Мемоизация<(int, int), int>
) -> int {
    let (н, к) = параметры
    
    match (н, к) {
        case (_, 0) | (н, к) if н == к => 1
        case (н, к) if к > н => 0
        case (н, к) => {
            return мемо.вызвать((н - 1, к - 1)) + мемо.вызвать((н - 1, к))
        }
    }
}

// Мемоизация наибольшей общей подпоследовательности (НОП)
function НОП_мемо_функция(
    параметры: (string, string),
    мемо: &mut Мемоизация<(string, string), int>
) -> int {
    let (строка1, строка2) = параметры
    
    if строка1.is_empty() || строка2.is_empty() {
        return 0
    }
    
    if строка1.chars().last() == строка2.chars().last() {
        let префикс1 = &строка1[..строка1.len() - 1]
        let префикс2 = &строка2[..строка2.len() - 1]
        return 1 + мемо.вызвать((префикс1.to_string(), префикс2.to_string()))
    } else {
        let префикс1 = &строка1[..строка1.len() - 1]
        let префикс2 = &строка2[..строка2.len() - 1]
        let случай1 = мемо.вызвать((строка1.clone(), префикс2.to_string()))
        let случай2 = мемо.вызвать((префикс1.to_string(), строка2.clone()))
        return std::cmp::max(случай1, случай2)
    }
} test {
    // Тест мемоизации фибоначчи
    let mut фибоначчиМемо = Мемоизация::новая(фибоначчи_мемо_функция)
    assert фибоначчиМемо.вызвать(10) == 55
    assert фибоначчиМемо.вызвать(20) == 6765
    
    // Тест мемоизации сочетаний
    let mut сочетанияМемо = Мемоизация::новая(сочетания_мемо_функция)
    assert сочетанияМемо.вызвать((5, 2)) == 10
    assert сочетанияМемо.вызвать((10, 3)) == 120
    
    // Тест мемоизации НОП
    let mut НОПМемо = Мемоизация::новая(НОП_мемо_функция)
    let результат = НОПМемо.вызвать(("АБВГДЕ".to_string(), "АЕГЖЗИ".to_string()))
    assert результат == 3  // "АЕГ"
}

// Пример использования
let mut фибоначчиМемо = Мемоизация::новая(фибоначчи_мемо_функция)
print("Мемоизированный фибоначчи(30): {фибоначчиМемо.вызвать(30)}")

let mut сочетанияМемо = Мемоизация::новая(сочетания_мемо_функция)
print("Сочетания C(20, 10): {сочетанияМемо.вызвать((20, 10))}")

🎯 Практические применения рекурсии

Возврат (Backtracking)

// Задача N ферзей
function решитьНФерзей(н: int) -> Array<Array<int>> {
    let mut решения: Array<Array<int>> = []
    let mut текущееРешение: Array<int> = vec![0; н]
    
    function возврат(ряд: int) {
        if ряд == н {
            решения.push(текущееРешение.clone())
            return
        }
        
        for столбец in 0..н {
            if безопаснаяПозиция(ряд, столбец, &текущееРешение) {
                текущееРешение[ряд] = столбец
                возврат(ряд + 1)
                // Возврат: автоматически восстанавливает предыдущее состояние
            }
        }
    }
    
    function безопаснаяПозиция(ряд: int, столбец: int, решение: &Array<int>) -> bool {
        for предыдущийРяд in 0..ряд {
            let предыдущийСтолбец = решение[предыдущийРяд]
            
            // Проверить тот же столбец
            if предыдущийСтолбец == столбец {
                return false
            }
            
            // Проверить диагонали
            if (предыдущийРяд - ряд).abs() == (предыдущийСтолбец as int - столбец as int).abs() {
                return false
            }
        }
        return true
    }
    
    возврат(0)
    return решения
}

// Поиск выхода из лабиринта
struct Лабиринт {
    сетка: Array<Array<bool>>,  // true = путь, false = стена
    высота: int,
    ширина: int
}

impl Лабиринт {
    function новый(сетка: Array<Array<bool>>) -> Лабиринт {
        let высота = сетка.len() as int
        let ширина = if высота > 0 { сетка[0].len() as int } else { 0 }
        
        return Лабиринт { сетка, высота, ширина }
    }
    
    function найтиПуть(&self) -> Option<Array<(int, int)>> {
        let mut посещённые: Array<Array<bool>> = vec![vec![false; self.ширина as usize]; self.высота as usize]
        let mut путь: Array<(int, int)> = []
        
        if self.DFS(0, 0, &mut посещённые, &mut путь) {
            return Some(путь)
        } else {
            return None
        }
    }
    
    function DFS(
        &self,
        ряд: int,
        столбец: int,
        посещённые: &mut Array<Array<bool>>,
        путь: &mut Array<(int, int)>
    ) -> bool {
        // Проверка границ
        if ряд < 0 || ряд >= self.высота || столбец < 0 || столбец >= self.ширина {
            return false
        }
        
        // Стена или уже посещено
        if !self.сетка[ряд as usize][столбец as usize] || посещённые[ряд as usize][столбец as usize] {
            return false
        }
        
        // Добавить текущую позицию в путь
        путь.push((ряд, столбец))
        посещённые[ряд as usize][столбец as usize] = true
        
        // Достигли пункта назначения
        if ряд == self.высота - 1 && столбец == self.ширина - 1 {
            return true
        }
        
        // Исследовать 4 направления
        let направления = [(0, 1), (1, 0), (0, -1), (-1, 0)]
        for (др, дс) in направления {
            if self.DFS(ряд + др, столбец + дс, посещённые, путь) {
                return true
            }
        }
        
        // Возврат: удалить из пути
        путь.pop()
        посещённые[ряд as usize][столбец as usize] = false
        
        return false
    }
} test {
    // Тест задачи 4 ферзей
    let решения = решитьНФерзей(4)
    assert решения.len() == 2  // У 4x4 есть 2 решения
    
    // Тест лабиринта
    let сеткаЛабиринта = [
        [true, false, true, true],
        [true, true, false, true],
        [false, true, true, true],
        [false, false, false, true]
    ]
    
    let мойЛабиринт = Лабиринт::новый(сеткаЛабиринта.map(|ряд| ряд.to_vec()).to_vec())
    let путь = мойЛабиринт.найтиПуть()
    assert путь.is_some()
}

// Пример использования
let решения = решитьНФерзей(8)  // Задача 8 ферзей
print("Количество решений 8 ферзей: {решения.len()}")

match решения.first() { case Some(первоеРешение) => print("Первое решение: {первоеРешение:?}"), case None => {} }

Динамическое программирование с рекурсией

// Задача о рюкзаке
struct Предмет {
    вес: int,
    ценность: int,
    название: string
}

function рюкзак_рекурсивно(
    предметы: &Array<Предмет>,
    вместимость: int,
    индекс: int = 0
) -> int {
    // Базовое условие
    if индекс >= предметы.len() || вместимость <= 0 {
        return 0
    }
    
    let текущийПредмет = &предметы[индекс]
    
    // Не можем включить текущий предмет
    if текущийПредмет.вес > вместимость {
        return рюкзак_рекурсивно(предметы, вместимость, индекс + 1)
    }
    
    // Выбрать максимум из двух вариантов
    let включитьСлучай = текущийПредмет.ценность + рюкзак_рекурсивно(
        предметы,
        вместимость - текущийПредмет.вес,
        индекс + 1
    )
    let исключитьСлучай = рюкзак_рекурсивно(предметы, вместимость, индекс + 1)
    
    return std::cmp::max(включитьСлучай, исключитьСлучай)
}

// Расстояние редактирования
function расстояниеРедактирования_рекурсивно(строка1: &str, строка2: &str) -> int {
    if строка1.is_empty() {
        return строка2.len() as int
    }
    if строка2.is_empty() {
        return строка1.len() as int
    }
    
    let символы1: Vec<char> = строка1.chars().collect()
    let символы2: Vec<char> = строка2.chars().collect()
    
    if символы1.last() == символы2.last() {
        let префикс1: String = символы1[..символы1.len()-1].iter().collect()
        let префикс2: String = символы2[..символы2.len()-1].iter().collect()
        return расстояниеРедактирования_рекурсивно(&префикс1, &префикс2)
    }
    
    let префикс1: String = символы1[..символы1.len()-1].iter().collect()
    let префикс2: String = символы2[..символы2.len()-1].iter().collect()
    
    let вставить = 1 + расстояниеРедактирования_рекурсивно(строка1, &префикс2)
    let удалить = 1 + расстояниеРедактирования_рекурсивно(&префикс1, строка2)
    let заменить = 1 + расстояниеРедактирования_рекурсивно(&префикс1, &префикс2)
    
    return std::cmp::min(вставить, std::cmp::min(удалить, заменить))
}

// Задача подъёма по ступенькам
function подъёмПоСтупенькам(число: int) -> int {
    match число {
        case 0 => 1
        case 1 => 1
        case 2 => 2
        case _ => подъёмПоСтупенькам(число - 1) + подъёмПоСтупенькам(число - 2)
    }
} test {
    // Тест задачи о рюкзаке
    let предметы = [
        Предмет { вес: 10, ценность: 60, название: "книга".to_string() },
        Предмет { вес: 20, ценность: 100, название: "компьютер".to_string() },
        Предмет { вес: 30, ценность: 120, название: "телевизор".to_string() }
    ]
    
    let максЦенность = рюкзак_рекурсивно(&предметы, 50)
    assert максЦенность == 220  // книга + компьютер = 60 + 100 + 60 = 220
    
    // Тест расстояния редактирования
    assert расстояниеРедактирования_рекурсивно("котёнок", "сидящий") == 8
    assert расстояниеРедактирования_рекурсивно("привет", "хорошо") == 6
    
    // Тест подъёма по ступенькам
    assert подъёмПоСтупенькам(1) == 1
    assert подъёмПоСтупенькам(3) == 3
    assert подъёмПоСтупенькам(5) == 8
}

print("Максимальная ценность рюкзака: {рюкзак_рекурсивно(&предметы, 50)}")
print("Расстояние редактирования (привет -> мир): {расстояниеРедактирования_рекурсивно("привет", "мир")}")
print("Способы подъёма на 10 ступенек: {подъёмПоСтупенькам(10)}")

🚨 Меры предосторожности при рекурсии

Предотвращение переполнения стека

// Опасная рекурсия - потенциальное переполнение стека
function опаснаяРекурсия(число: int) -> int {
    if число <= 0 {
        return 0
    }
    return 1 + опаснаяРекурсия(число - 1)  // Переполнение стека для больших n
}

// Безопасная итеративная версия
function безопаснаяИтерация(число: int) -> int {
    let mut результат = 0
    for и in 1..=число {
        результат += 1
    }
    return результат
}

// Безопасная рекурсия с проверкой переполнения стека
function безопаснаяРекурсия(число: int, глубина: int = 0) -> Result<int, string> {
    const МАКС_ГЛУБИНА: int = 1000
    
    if глубина > МАКС_ГЛУБИНА {
        return Err("Превышена глубина рекурсии: риск переполнения стека".to_string())
    }
    
    if число <= 0 {
        return Ok(0)
    }
    
    match безопаснаяРекурсия(число - 1, глубина + 1) {
        case Ok(результат) => Ok(1 + результат)
        case Err(ошибка) => Err(ошибка)
    }
}

// Техника батута для предотвращения переполнения стека
enum Батут<T> {
    Готово(T),
    Продолжить(Box<dyn FnOnce() -> Батут<T>>)
}

function выполнитьБатут<T>(mut результат: Батут<T>) -> T {
    loop {
        match результат {
            case Батут::Готово(значение) => return значение
            case Батут::Продолжить(функция) => результат = функция()
        }
    }
}

function факториал_батут(число: int, аккумулятор: int = 1) -> Батут<int> {
    if число <= 1 {
        return Батут::Готово(аккумулятор)
    }
    
    return Батут::Продолжить(Box::new(move || {
        факториал_батут(число - 1, аккумулятор * число)
    }))
} test {
    // Тест безопасной рекурсии
    assert безопаснаяРекурсия(100).is_ok()
    assert безопаснаяРекурсия(2000).is_err()
    
    // Тест техники батута
    let результат = выполнитьБатут(факториал_батут(10))
    assert результат == 3628800
}

print("Безопасная рекурсия (100): {безопаснаяРекурсия(100).unwrap()}")
print("Батут факториал(10): {выполнитьБатут(факториал_батут(10))}")

🎨 Функциональные паттерны рекурсии

Функции высшего порядка и рекурсия

// Рекурсивная реализация map
function рекурсивныйMap<T, U>(
    массив: Array<T>,
    функцияПреобразования: fn(T) -> U,
    индекс: int = 0
) -> Array<U> {
    if индекс >= массив.len() {
        return []
    }
    
    let mut результат = [функцияПреобразования(массив[индекс].clone())]
    результат.extend(рекурсивныйMap(массив, функцияПреобразования, индекс + 1))
    return результат
}

// Рекурсивная реализация filter
function рекурсивныйFilter<T>(
    массив: Array<T>,
    функцияПредиката: fn(&T) -> bool,
    индекс: int = 0
) -> Array<T> {
    if индекс >= массив.len() {
        return []
    }
    
    let текущийЭлемент = &массив[индекс]
    let оставшийсяРезультат = рекурсивныйFilter(массив.clone(), функцияПредиката, индекс + 1)
    
    if функцияПредиката(текущийЭлемент) {
        let mut результат = [текущийЭлемент.clone()]
        результат.extend(оставшийсяРезультат)
        return результат
    } else {
        return оставшийсяРезультат
    }
}

// Рекурсивная реализация reduce
function рекурсивныйReduce<T, U>(
    массив: Array<T>,
    начальноеЗначение: U,
    функцияСведения: fn(U, T) -> U,
    индекс: int = 0
) -> U {
    if индекс >= массив.len() {
        return начальноеЗначение
    }
    
    let новоеНачальноеЗначение = функцияСведения(начальноеЗначение, массив[индекс].clone())
    return рекурсивныйReduce(массив, новоеНачальноеЗначение, функцияСведения, индекс + 1)
}

// Реализация Y-комбинатора
function Y<F, T>(функция: F) -> impl Fn(T) -> T
where
    F: Fn(&dyn Fn(T) -> T, T) -> T,
    T: 'static
{
    move |ввод| {
        let рекурсивнаяФункция = |себя: &dyn Fn(T) -> T, параметр: T| -> T {
            функция(себя, параметр)
        }
        рекурсивнаяФункция(&рекурсивнаяФункция, ввод)
    }
} test {
    let числа = [1, 2, 3, 4, 5]
    
    // Тест рекурсивного map
    let квадраты = рекурсивныйMap(числа.clone(), |x| x * x)
    assert квадраты == [1, 4, 9, 16, 25]
    
    // Тест рекурсивного filter
    let чётные = рекурсивныйFilter(числа.clone(), |&x| x % 2 == 0)
    assert чётные == [2, 4]
    
    // Тест рекурсивного reduce
    let сумма = рекурсивныйReduce(числа.clone(), 0, |акк, x| акк + x)
    assert сумма == 15
}

print("Рекурсивный map (квадраты): {рекурсивныйMap([1, 2, 3, 4], |x| x * x)}")
print("Рекурсивный filter (чётные): {рекурсивныйFilter([1, 2, 3, 4, 5, 6], |&x| x % 2 == 0)}")
print("Рекурсивный reduce (сумма): {рекурсивныйReduce([1, 2, 3, 4, 5], 0, |акк, x| акк + x)}")

🎯 Мастерство рекурсии

Рекурсия — мощный инструмент для элегантного решения сложных задач:

✅ Когда использовать рекурсию:

  • Обработка древовидных/графовых структур
  • Алгоритмы разделяй и властвуй
  • Задачи возврата (backtracking)
  • Математические определения, которые рекурсивны

⚠️ Меры предосторожности при рекурсии:

  • Чётко определить базовые условия
  • Предотвратить переполнение стека
  • Рассмотреть оптимизацию производительности (мемоизация, хвостовая рекурсия)
  • Учесть сложность отладки

🚀 Преимущества рекурсии в Топазе:

  • Сопоставление с образцом для чёткого разделения условий
  • Типобезопасность для предотвращения ошибок
  • Поддержка парадигмы функционального программирования
  • Автоматическая оптимизация хвостовой рекурсии

Исследуйте прекрасный мир рекурсии с Топазом! 🌀✨