Table of Contents:

特点:
- 强调将计算过程视(功能的实现)用多个不同功能的函数组合而成,其中函数一般逻辑计较固定,简单,易懂,是对常用逻辑的抽象。
- 在过程式编程中,逻辑往往藏在循环的深处,理解过程较长,而函数编程通过常用的函数而大大简化逻辑
- 避免修改状态和可变数据,从而减少副作用
- 强调不修改现有数据,而是创建新的数据。使用不可变性可以降低错误发生的可能性。

在 C++ 里,算法的地位非常高,甚至有一个专门的“算法库”。早期,它是泛型编程的示范和应用,而在 C++ 引入 lambda 表达式后,它又成了函数式编程的具体实践,所以,学习掌握算法能够很好地训练你的编程思维,帮你开辟出面向对象之外的新天地

for_each

vector<int> v = {3,5,1,7,10};   // vector容器
for(const auto& x : v) {        // range for循环
    cout << x << ",";
}
auto print = [](const auto& x)  // 定义一个lambda表达式
{
    cout << x << ",";
};
for_each(cbegin(v), cend(v), print);// for_each算法
for_each(                      // for_each算法,内部定义lambda表达式
    cbegin(v), cend(v),        // 获取常量迭代器
    [](const auto& x)          // 匿名lambda表达式
    {
        cout << x << ",";
    }
);

初看上去 for_each 算法显得有些累赘,既要指定容器的范围,又要写 lambda 表达式,没有 range-for 那么简单明了。
对于很简单的 for 循环来说,确实是如此,我也不建议你对这么简单的事情用 for_each 算法。
但更多的时候,for 循环体里会做很多事情,会由 if-else、break、continue 等语句组成很复杂的逻辑。
但是for_each 把逻辑拆成两个:一个遍历容器元素,另一个操纵容器元素,而且名字的含义更明确,代码也有更好的封装。

映射(Map)

在函数范式中,你可以使用std::transform函数来实现映射操作,将一个容器中的每个元素映射为另一个容器中的元素。例如,将一个整数数组的每个元素加倍:

#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    std::vector<int> doubled_numbers;

    std::transform(numbers.begin(), numbers.end(), std::back_inserter(doubled_numbers),
                   [](int num) { return num * 2; });

    for (int num : doubled_numbers) {
        std::cout << num << " ";
    }

    return 0;
}

过滤(Filter)

使用std::copy_if函数来实现过滤操作,从一个容器中复制满足特定条件的元素到另一个容器。例如,从一个整数数组中过滤出偶数:

#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6};
    std::vector<int> even_numbers;

    std::copy_if(numbers.begin(), numbers.end(), std::back_inserter(even_numbers),
                 [](int num) { return num % 2 == 0; });

    for (int num : even_numbers) {
        std::cout << num << " ";
    }

    return 0;
}

对容器的计算

对容器中的数据进行计算

int n2 = 0;
for(auto x : v) { 
    if (x == 1) {
        n2++;
    }
}

//统计等于 1 的元素个数
vector<int> v = {1,3,1,7,5}; 
auto n1 = std::count( 
    begin(v), end(v), 1 
);

//配合lambda效果更好
auto n = std::count_if( 
    begin(v), end(v),
    [](auto x) { 
        return x > 2;
    }
);

递归(Recursion)

递归是函数范式中常见的方法,它可以用来解决许多问题,例如计算阶乘:

#include <iostream>

int factorial(int n) {
    if (n == 0 || n == 1) {
        return 1;
    } else {
        return n * factorial(n - 1);
    }
}

int main() {
    int n = 5;
    std::cout << "Factorial of " << n << " is: " << factorial(n) << std::endl;

    return 0;
}

递归结构处理,用于处理树形结构等递归定义的数据

#include <iostream>
#include <vector>

struct TreeNode {
    int value;
    std::vector<TreeNode> children;
};

int sum_tree(const TreeNode& node) {
    int sum = node.value;
    for (const TreeNode& child : node.children) {
        sum += sum_tree(child);
    }
    return sum;
}

int main() {
    TreeNode root{1, {{2, {}}, {3, {{4, {}}, {5, {}}}}}};
    std::cout << "Sum of tree nodes: " << sum_tree(root) << std::endl;

    return 0;
}

高阶函数

高阶函数(Higher-order Function)是指能够接受一个或多个函数作为参数,并/或返回一个函数作为结果的函数。换句话说,它可以将函数作为数据来操作和传递,就像操作其他数据类型一样。高阶函数是函数式编程的一个重要概念,它可以让代码更具有抽象性、模块化和灵活性。

在高阶函数中,函数被视为一等公民(First-Class Citizens),这意味着它们可以像其他数据类型一样被传递、赋值、储存在变量中,并且可以作为参数传递给其他函数。

高阶函数的使用可以提高代码的可读性、可维护性和重用性,因为它们鼓励将逻辑分解为更小的可组合部分,从而形成更简洁、模块化的代码。

常见用法

1.函数作为参数
在高阶函数中,你可以将一个函数作为参数传递给另一个函数。这使得你能够将一些通用的逻辑传递给一个更具体的函数,从而实现更大程度的抽象和可重用性。

#include <iostream>
#include <functional>

void apply_twice(std::function<int(int)> func, int num) {
    std::cout << func(func(num)) << std::endl;
}

int add_one(int x) {
    return x + 1;
}

int main() {
    apply_twice(add_one, 2); // Output: 4 (2 + 1 = 3, 3 + 1 = 4)
    return 0;
}

2.函数作为返回值
高阶函数也可以返回一个函数,这使得你能够动态地创建函数,根据不同的场景返回不同的计算逻辑。

#include <iostream>
#include <functional>

std::function<int(int)> multiplier(int factor) {
    return [=](int x) { return x * factor; };
}

int main() {
    auto double_value = multiplier(2);
    std::cout << double_value(5) << std::endl; // Output: 10 (5 * 2)
    return 0;
}

3.函数组合
高阶函数可以用来将多个函数组合成一个新的函数,从而实现更复杂的操作。

#include <iostream>
#include <functional>

std::function<int(int)> compose(std::function<int(int)> f, std::function<int(int)> g) {
    return [=](int x) { return f(g(x)); };
}

int add_one(int x) {
    return x + 1;
}

int multiply_by_two(int x) {
    return x * 2;
}

int main() {
    auto add_then_double = compose(multiply_by_two, add_one);
    std::cout << add_then_double(5) << std::endl; // Output: 12 (5 + 1 = 6, 6 * 2 = 12)
    return 0;
}

柯里化

柯里化(Currying)是函数式编程中的一个概念,它指的是将一个接受多个参数的函数转化为一系列接受单个参数的函数。这样做的好处之一是可以创建更高阶、更灵活的函数。柯里化的核心思想是将多参数函数转化为一系列单参数函数,每个单参数函数都返回一个新的函数,直到所有参数都被传递完毕,然后执行最终的计算。
柯里化的优点在于它可以使函数的参数传递变得更加灵活和模块化。通过将一个函数拆分为多个步骤,可以更容易地进行部分参数应用,复用函数逻辑,以及创建更高阶的函数组合。

#include <iostream>
#include <functional>

int add(int a, int b) {
    return a + b;
}

std::function<int(int)> curried_add(int a) {
    return [=](int b) { return add(a, b); };
}

int main() {
    auto add_five = curried_add(5);
    std::cout << "Result: " << add_five(3) << std::endl;

    return 0;
}

惰性计算

惰性计算(Lazy Evaluation)是一种编程策略,它在需要计算某个值时才执行实际的计算,而不是立即计算。这种延迟计算的方式可以优化性能和资源使用,因为它避免了不必要的计算,节省了时间和内存。惰性计算通常在函数式编程中广泛使用,但也可以在其他编程范式中应用。

惰性计算的核心思想是推迟计算,直到真正需要计算结果的时候才进行。这可以通过使用闭包、函数、类等方式来实现。在惰性计算中,一些计算步骤可能会被跳过,直到结果被请求。
总结一下
惰性计算可以分成两个阶段:
- 生成阶段:通过组合函数对象、闭包、Lambda等,生成一个高价函数
- 计算阶段:执行之前生成的高价函数

#include <iostream>
#include <functional>

std::function<int(int)> lazy_fibonacci(int n) {
    if (n == 0) {
        return [=]() { return 0; };
    } else if (n == 1) {
        return [=]() { return 1; };
    } else {
        std::function<int()> prev = lazy_fibonacci(n - 1);
        std::function<int()> prev_prev = lazy_fibonacci(n - 2);

        return [=]() { return prev() + prev_prev(); };
    }
}

int main() {
    int n = 5;
    std::function<int()> fibonacci = lazy_fibonacci(n);

    std::cout << "Fibonacci(" << n << "): " << fibonacci() << std::endl;

    return 0;
}

js中的函数编程

every

测试数组的所有元素是否都通过了指定函数的测试。

some

测试数组中的某些元素是否通过了指定函数的测试。

forEach

令数组的每一项都执行指定的函数。

map

返回一个由原数组中每个元素调用某个指定方法得到的返回值所组成的新数组,返回每一个处理结果。

filter

利用所有通过指定函数处理的元素创建一个新的数组并返回。

reduce

接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终缩减为一个值。

let colors = ['red', 'blue', 'green', 'green'];
console.log(colors.indexOf('green')); // 2
console.log(colors.lastIndexOf('green')); // 3
console.log(colors.every(function (color) {
    return color.length >= 3;
})); // true,判断是否所有的元素长度均大于等于 3
console.log(colors.some(function (color) {
    return color.length > 4;
})); // true,判断是否有至少一个元素长度大于 4
colors.forEach(function (color) {
    if (color === 'green') {
        console.log(color);
    }
}); // green green
console.log(colors.map(function (color) {
    if (color === 'green') {
        return color;
    }
})); // 输出数组:[undefined, undefined, "green", "green"]
console.log(colors.filter(function (color) {
    if (color === 'green') {
        return color;
    }
})); // 输出数组:["green", "green"]
console.log(colors.reduce(function (color, nextColor) {
    return color + ',' + nextColor;
})); // 输出字符串: red,blue,green,green
console.log(colors.reduceRight(function (color, nextColor) {
    return color + ',' + nextColor;
})); //输出字符串: green,green,blue,red