Matlab 函數式編程


作為一個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         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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM