cuiguanjun 2020-03-01
作为一个Mathematica的熟练使用者,在切换到Matlab时总会经常产生编程习惯上的“水土不服”。利用Mathematica强大而丰富的内置函数,我们可以以简洁的代码实现复杂的功能。相比之下,Matlab的灵活性就欠缺很多。
为此,本文旨在讨论如何利用Matlab的匿名函数实现类似Mathematica的函数式编程。这里主要使用的是Tucker McClure所编写的函数式编程工具。
下面的代码使用匿名函数同时获取向量中的最大值和最小值:
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 );
这个函数看起来很奇怪,但是使用起来很方便。如果我们要实现下面的判断:
可以用下面的代码实现:
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