Fold

fold используется для превращения множества в 1 значение.

Базовый синтаксис

fn fold<B, F>(self, init: B, f: F) -> B
where
    F: FnMut(B, Self::Item) -> B,
  • init: исходное значение счётчика;
  • f: замыкание, которое тоже принимает 2 аргумента: счётчик и объект. Замыкание возвращает счётчик.

Как работает

fold() преобразовывает каждый элемент по цепочке :

  1. Начинает со значения  init в счётчике;
  2. Для каждого элемента применяет замыкание f(accumulator, element);
  3. Возвращает финальное значение счётчика после обработки всех элементов. Наглядно по шагам:
fold(init, |acc, x| f(acc, x))
// Шаг 1: acc = init
// Шаг 2: acc = f(init, item1)
// Шаг 3: acc = f(acc, item2)
// Шаг 4: acc = f(acc, item3)
// ...
// Возврат финального acc

Сумма чисел:

let numbers = vec![1, 2, 3, 4, 5];  
let sum = numbers.iter().fold(0, |acc, &x| acc + x);
println!("Sum: {}", sum); // 15    
// По шагам:
// acc = 0
// acc = 0 + 1 = 1
// acc = 1 + 2 = 3
// acc = 3 + 3 = 6
// acc = 6 + 4 = 10
// acc = 10 + 5 = 15
// код выше можно заменить на sum()

Произведение чисел:

let numbers = vec![1, 2, 3, 4, 5];
let product = numbers.iter().fold(1, |acc, &x| acc * x);
println!("Product: {}", product); // 120
// код выше можно заменить на product()

Нахождение максимального значения

let numbers = vec![3, 1, 4, 1, 5, 9, 2];
let max = numbers.iter().fold(i32::MIN, |acc, &x| acc.max(x));
println!("Max: {}", max); // 9

Соединение (конкатенация) строк

Счётчик у fold необязательно числовой, можно использовать, например, String:

fn hello_world_concat() {
	let words = vec!["hello", "world", "rust"];    
	let sentence = words.iter().fold(String::new(), |acc, &word| {
	if acc.is_empty() {
	    word.to_string()
	    } else {
	    acc + " " + word
	    }});
	println!("Sentence: '{}'", sentence); // "hello world rust"
}

fn giant_grunts(initial: char) -> String {
    ["Bee", "Fee", "Gee", "Fi", "Hi", "Fo", "Mo", "Fum", "Tum"].iter().fold(
        String::new(),
        |acc, grunt| if grunt.starts_with(initial) { acc + grunt } else { acc },
    )}

fn main() {
    let song = giant_grunts('F');
    println!("{song:?}"); // "FeeFiFoFum" 
}

Разделение на чётные и нечётные

    let numbers = vec![1, 2, 3, 4, 5];
    let result = numbers.iter().fold(
        (Vec::new(), Vec::new()), // (evens, odds)
        |(mut evens, mut odds), &x| {
            if x % 2 == 0 {
                evens.push(x);
            } else {
                odds.push(x);
            }
            (evens, odds)
        }
    );
    
    println!("Evens: {:?}, Odds: {:?}", result.0, result.1);
    // Evens: [2, 4], Odds: [1, 3, 5]

Получение частоты символов в тексте:

use std::collections::HashMap;
pub fn first_non_repeating(s: &str) -> Option<HashMap<char, i32>> {
    let char_frequency = s.chars().fold(HashMap::new(), |mut acc, char| {
        *acc.entry(char).or_insert(0) += 1;
        acc
    });
    Some(char_frequency)
}

fn main() {
    println!("Letter frequency: {:?}", first_non_repeating("stress"));
} // Letter frequency: Some({'e': 1, 'r': 1, 's': 3, 't': 1})

Реализация других методов

Реализация map()

let numbers = vec![1, 2, 3, 4, 5];
let doubled: Vec<i32> = numbers.iter().fold(Vec::new(), |mut acc, &x| {
    acc.push(x * 2);
    acc });
    println!("Doubled: {:?}", doubled); // [2, 4, 6, 8, 10]

Реализация filter()

let numbers = vec![1, 2, 3, 4, 5, 6];    
    let evens: Vec<i32> = numbers.iter().fold(Vec::new(), |mut acc, &x| {
        if x % 2 == 0 {
            acc.push(x); }
        acc });
println!("Evens: {:?}", evens); // [2, 4, 6]

Вариация try_fold()

От fold() отличается тем, что прерывает выполнение и возвращает Result(Err):

    let strings = ["1", "2", "3", "4", "5"];
    // перевести строки в числа и суммировать их
    let sum: Result<i32, _> = strings
        .iter()
        .try_fold(0, |acc, &s| match s.parse::<i32>() {
            Ok(num) => Ok(acc + num),
            Err(e) => Err(e),
        });

    match sum {
        Ok(total) => println!("Total: {}", total), // 15
        Err(e) => println!("Parse error: {}", e), }