作為一個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 );
這個函數看起來很奇怪,但是使用起來很方便。如果我們要實現下面的判斷:
- 出現無窮值,報錯;
- 所有值都是0,返回0;
- 否則,輸出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 0
normalize([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 2
ans =
1 6
5 7
ans =
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