Matlab 函数式编程

cuiguanjun 2020-03-01

作为一个Mathematica的熟练使用者,在切换到Matlab时总会经常产生编程习惯上的“水土不服”。利用Mathematica强大而丰富的内置函数,我们可以以简洁的代码实现复杂的功能。相比之下,Matlab的灵活性就欠缺很多。

为此,本文旨在讨论如何利用Matlab的匿名函数实现类似Mathematica的函数式编程。这里主要使用的是Tucker McClure所编写的函数式编程工具。

 Map

下面的代码使用匿名函数同时获取向量中的最大值和最小值:

min_and_max = @(x) [min(x), max(x)];
min_and_max([3 4 1 6 2])
ans =     1     6

看起来已经足够简洁,但是min和max函数本身可以除了输出最大/小值之外,还可以输出这些值的位置。如果希望使用匿名函数实现上述功能,就需要下面这段代码:

[extrema, indices] = cellfun(@(f) f([3 4 1 6 2]), {@min, @max})
extrema =     1     6
indices =     3     4

 上述代码看起来有点奇怪,cellfun作用到了一系列函数句柄上,而不是通常的元素数组,而匿名函数的变量是另一个函数。实际上,我们并不是对数据进行操作,而是在对函数句柄进行操作。上述代码可以进一步构造成下面的形式:

min_and_max = @(x) cellfun(@(f) f(x), {@min, @max});

 这样我们就获得了一个强大的新函数,它复合了min和max的功能:

y = randi(10, 1, 10)
just_values        = min_and_max(y)
[~, just_indices]  = min_and_max(y)
[extrema, indices] = min_and_max(y)
y =     9    10     2    10     7     1     3     6    10    10
just_values =     1    10
just_indices =     6     2
extrema =     1    10
indices =     6     2

 我们定义的新的min_and_max函数实际上是将基本的min和max函数“map”到了数组上,这正是Mathematica中Map函数的功能。

我们可以更进一步,在Matlab中定义一个通用的Map函数:

map = @(val, fcns) cellfun(@(f) f(val{:}), fcns);

 这样,之前的min_and_max函数可以重新写成下面的形式:

x = [3 4 1 6 2];
[extrema, indices] = map({x}, {@min, @max})

 这个map函数可以映射多个函数到数组上:

map({1, 2}, {@plus, @minus, @times})
ans =     3    -1     2

 如果每个函数的输出大小不相等,可以用下面的mapc函数:

mapc = @(val, fcns) cellfun(@(f) f(val{:}), fcns, ‘UniformOutput‘, false);

mapc({pi}, {@(x) 2 * x, ...                     % Multiply by 2
            @cos, ...                           % Find cosine
            @(x) sprintf(‘x is %.5f...‘, x)})   % Return a string

ans =  1×3 cell 数组
    {[6.2832]}    {[-1]}    {‘x is 3.14159...‘}

 这个函数将每个函数的输出合并到一个cell数组中。

正如上面所展示的,这种“作用于函数的函数”正是函数式编程思想的核心所在。

行内条件语句

Matlab的匿名函数并不支持条件语句,但是我们可以通过下面的函数进行变通:

iif = @(varargin) varargin{2 * find([varargin{1:2:end}], 1, ‘first‘)}();[out1, out2, ...] = iif( if this,      then run this, ...                            else if this, then run this, ...                            ...                            else,         then run this );

 这个函数看起来很奇怪,但是使用起来很方便。如果我们要实现下面的判断:

  1. 出现无穷值,报错;
  2. 所有值都是0,返回0;
  3. 否则,输出x/norm(x);

可以用下面的代码实现:

normalize = @(x) iif( ~all(isfinite(x)), @() error(‘Must be finite!‘), ...
                                  all(x == 0),       @() zeros(size(x)), ...
                                  true,              @() x/norm(x) );normalize([1 1 0])ans =    0.7071    0.7071         0normalize([0 0 0])ans =     0     0     0

 匿名函数的迭代

我们可以定义一个迭代函数,这个函数会调用它自身:

recur = @(f, varargin) f(f, varargin{:});

 用这个函数实现斐波那契数列的计算:

fib = @(n) recur(@(f, k) iif(k <= 2, 1, ...
                             true,   @() f(f, k-1) + f(f, k-2)), ...
                 n);arrayfun(fib, 1:10)ans =     1     1     2     3     5     8    13    21    34    55

 计算阶乘:

factorial = @(n) recur(@(f, k) iif(k == 0, 1, ...
                                   true,   @() k * f(f, k-1)), n);
arrayfun(factorial, 1:7)

 辅助函数

下面两个辅助函数将小括号和大括号转换为函数形式:

paren = @(x, varargin) x(varargin{:});
curly = @(x, varargin) x{varargin{:}};

 这两个函数可以为我们带来很多便利,是我们可以不用定义中间变量就获得想要的值:

magic(3)
paren(magic(3), 1:2, 2:3)
paren(magic(3), 1:2, ‘:‘)ans =     8     1     6     3     5     7     4     9     2ans =     1     6     5     7ans =     8     1     6     3     5     7

 curly还有一个作用,就是将几个语句合并到一起:

dots = @() curly({...
    figure(‘Position‘, [0.5*screen_size() - [100 50], 200, 100], ...
           ‘MenuBar‘,  ‘none‘), ...                % Position the figure
    plot(randn(1, 100), randn(1, 100), ‘.‘)}, ...  % Plot random points
    ‘:‘);                                          % Return everything

[h_figure, h_dots] = dots()

 更多内容可参考:https://www.mathworks.com/matlabcentral/fileexchange/39735-functional-programming-constructs

相关推荐