Matlab 新学到的

通过调试LSTM的一个程序,学习到一些新的matlab技巧,顿时觉得, 自己之前会的简直是垃圾,鄙视自己3秒钟……
所以会陆续将新接触的技巧记录下来

1. varargin

可变长度输入参数列表

% 使用多个输入调用该函数。
function acceptVariableNumInputs(varargin)
    disp("Number of input arguments: " + nargin)
    celldisp(varargin)
end
% 调用
acceptVariableNumInputs(ones(3),'some text',pi)
% 输出结果
Number of input arguments: 3
varargin{1} =

     1     1     1
     1     1     1
     1     1     1
varargin{2} = some text 
varargin{3} =    3.1416

可变数目的输入和输出

在名为 variableNumInputAndOutput.m 的文件中定义一个函数,它接受可变数目的输入和输出。

function varargout = variableNumInputAndOutput(varargin)
    disp(['Number of provided inputs: ' num2str(length(varargin))])
    disp(['Number of requested outputs: ' num2str(nargout)])

    for k = 1:nargout
        varargout{k} = k;
    end
end
% 使用两个输入和三个输出调用该函数。
[d g p] = variableNumInputAndOutput(6,'Nexus')
Number of provided inputs: 2
Number of requested outputs: 3
d = 1
g = 2
p = 3

2. 理解cell的()引用和{}引用的区别

理解Matlab传递参数时的逗号语义,以及相关的用cell实现变长参数的传递。例如

x = linspace(0, 2*pi);
sty = {'linewidth', 2, 'color','r', 'linestyle','--'};
plot(x, sin(x), sty{:});

可以用 cell 和 函数句柄实现类似于函数式编程的操作,以 fibonacci 数列为例:

>> fib = @(n)f{(n>2)+1}(f,n);
>> fib(20)
ans =
        6765

类似的,构建 if 函数:

iff = @(varargin)varargin{find([varargin{1:2:end}],1)*2};
x = 3;
iff(x<1,1,x<2,2,x<3,3,x<4,4,x<5,5)
ans =
4

3. 函数管理

代码规模较大时,做好名字空间管理。
如果有大量互相独立的短函数,用静态方法 (methods(Static)) 实现名字空间的管理。
调用时:
xy = xxu.fun_1(A);
vv = xxu.fun_2(A);

classdef xxu
  methods(Static)
    function r = fun_1 (arg)
    end

    function v = fun_2 (arg)
    end
  end
end

如果函数多,每个函数也比较大,(比如一个函数就是一个几百行的文件,里面还有sub function, nested function),用pakage管理。
例如新建一个叫 “+xxu”的文件夹(xxu前面一个加号),文件夹里面有函数文件fun_1.m和fun_2.m,调用时就可以用名字空间xxu:
xy = xxu.fun_1(A);
vv = xxu.fun_2(A);
如果直接写fun_1或fun_2就会出错。

4. 匿名函数 cellfun(@(f) f(), )

匿名函数的优点在于使用方便,不需要保存成一个单独的文件。
对于一些只使用一次,并且结构简单的函数来说,这样做可以避免整个工程的文件系统过于混乱。

最大最小值函数

1.我们来写一个函数,找到数组中的最大和最小值并且将其存在另一个数组里。如下,

% ------- 我以前不知道如何用匿名函数返回两个以上的返回值 ----------
K>> min_and_max = @(x) [min(x), max(x)];
K>> min_and_max([3 4 1 6 2])
ans =
     1     6
K>> min_and_max = @(x) {min(x), max(x)}; % another version
K>> min_and_max([3 4 1 6 2])
ans = 
    [1]    [6]
K>> celldisp(min_and_max([3 4 1 6 2]))
ans{1} =
     1
ans{2} = 
     6

2.这个函数很简单。我们再写一个复杂的,让函数不但返回最大最小值,而且要返回这些值的位置。
% 这样,我们的匿名函数就得有两个输出,

% ----- 下面cellfun中的f是个函数指针(函数句柄), cellfun完成函数指针的调用 -------
[extrema, indices] = cellfun(@(f) f([3 4 1 6 2]), {@min, @max})

这个函数看起来很奇怪,首先,我们调用了cellfun这个函数。

第一个参数是一个函数句柄(函数指针)
第二个参数是任何一个cell。这里,第二个参数的cell里面是两个函数句柄,原来cellfun还可以这样用!

这样,第二个输入中的函数句柄会一次传递给第一个输入中的f,从而实现多个输出。
我们可以把上面那个句子中的 f([3 4 1 6 2])替换成f(x),这样,这个min_and_max就成了一个可以使用的函数

cellfun的第一个参数是个函数指针,下面匿名函数@(f) f(x)就是函数指针,这个匿名函数的参数也是函数指针——-

K>> min_and_max = @(x) cellfun(@(f) f(x), {@min, @max})
K>> y = randi(10, 1, 10)
y =     9    10     2    10     7     1     3     6    10    10

K>> just_values = min_and_max(y)
just_values =
     1    10

K>> [~, just_indices] = min_and_max(y)
just_indices =
     6     2

K>> [extrema, indices] = min_and_max(y)
extrema =
     1    10
indices =
     6     2
映射

我们定义val为一个cell数组,里面包含了我们的输入数据,fcns是一系列函数句柄

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

这个写法有什么用呢?我们可以通过这种方式改写一下上面的min_and_max函数,让它变得更短一些。
我们现在要将x通过max和min函数进行映射


K>> x = [3 4 1 6 2];
K>> map = @(val, fcns) cellfun(@(f) f(val{:}), fcns); 
K>> [extrema, indices] = map({x}, {@min, @max})
extrema =
     1     6

indices =
     3     4

下面的例子的目的:给函数传入数据的同时传入操作这些数据的函数指针
我们做个实验,将pi送到多个函数里面,输出完全不兼容的结果,包括数字和字符串

K>> mapc = @(val, fcns) cellfun(@(f) f(val{:}), fcns, 'UniformOutput', false); 
K>> mapc({pi}, {@(x) 2 * x, ...                     % Multiply by 2
            @cos, ...                           % Find cosine
            @(x) sprintf('x is %.5f...', x)})   % Return a string
ans = 
    [6.283185307179586]    [-1]    'x is 3.14159...'
内嵌条件语句 inline if (i.e. iif)

有时候我们也许想在匿名函数里面使用if else之类的语句。然而,普通的Matlab语法并不允许在匿名函数中写入这样的句子。
为了解决这一问题,我们单独写一个“inline if”这样的内嵌条件函数,

iif = @(varargin) varargin{2 * find([varargin{1:2:end}], 1, 'first')}(); %是一个很好用的条件判断函数
iif = @(varargin) varargin{2 * find([varargin{1:2:end}], 1)}()
    % I = find(X,K) returns at most the first K indices corresponding to the nonzero entries of the array X.  
    % K must be a positive integer
    % I = find(X,K,'first') is the same as I = find(X,K).

% 这个函数看起来就更难以理解了,在理解它之前,先看看它有多好用,这样就有理解的动力了。

 [out1, out2, ...] = iif( if this,      then run this, ...
                            else if this, then run this, ...
                            ...
                            else,         then run this ); 
% "if this" 条件中,第一个取true的条件,其后面的 "then run this" 的句子 就会被执行,其他的都不会被执行。不管写多少行都没问题。

我们可以用这个函数来做一个安全的归一化功能,如下:

如果不是所有x都为有限值,则报错
否则,如果所有x都为0,则返回0
否则,返回 x/norm(x).

下面就是这个功能的写法。注意,每个动作语句前面都加上了 @(). 这是为了将一个语句变成一个匿名函数的句柄,这样才可以正确的输入到 iif 函数里面。下面的philosophy是对数据进行判断,然后进行相应的操作,其中@() x/norm(x)都是匿名函数句柄

normalize = @(x) iif( ~all(isfinite(x)), @() error('Must be finite!'), ...
                      all(x == 0),       @() zeros(size(x)), ...
                      true,              @() x/norm(x) );

虽然这个函数完全不必要用匿名函数这样写,但是这个例子说明了 iif 这个函数映射的作用还是很强大的。我们下面看一下它的原理,

-首先,iif 函数通过varargin接受任意数量的输入. 这些输入将会被解析为条件,行为;条件,行为;……这样的形式。
-然后,iif 选择了所有的条件,也就是varargin中奇数位置的那些项,进行判断,对于我们上面的例子,取了这些条件 [~all(isfinite(x)), all(x == 0), true]
-下一步,它找到了第一个为true的条件的位置,例如, if~all(isfinite(x)) 是 false, 但是 all(x == 0) 是 true, 所以位置是2
-最后,我们通过位置定位到相应的动作语句,然后执行。由于我们把语句定义成了函数句柄,所以在后面家一个()就可以执行了。也就是varargin{…}()

匿名函数递归

由于递归函数是调用自身的函数,那么我们就需要一种能够让函数引用自己的办法。然而,当我们写匿名函数的时候,它并没有函数名,我们如何来调用自己呢?
先看一个简单的菲波纳契数列的例子。菲波纳契数列从1,1开始,之后每一个数等于前面两个数之和。

% % 用计算机实现这个是再简单不过的。我们试试下面这种递归方式
fib = @(n) iif(n <= 2,    1, ...                    % First two numbers
                  true,   @() fib(n-1) + fib(n-2)); % All later numbers
% % 我们还没有定义fib,怎么就在里面引用了?这种方式肯定是不行的。我们不能直接调用fib,得使用另一个输入,也就是调用一个函数句柄 f!
% ---- 这个例子说明同时传入函数句柄和数据参数的妙用 -----------
fib = @(f, n) iif(n <= 2, 1, ...                      % First two numbers
                  true,   @() f(f, n-1) + f(f, n-2)); % All later numbers
% 将 fib句柄以及一个数字传入这个fib函数,它将自身调用两次,从而实现递归(递归是"回溯"+"递推")。               
fib(fib, 6)  %但是这个函数看起来很别扭。我们需要将自己作为参数传给自己?



% 我们来写另一个简单一点的函数,可以帮我们做这个工作。我们需要指定n,它可以自己调用上面的语句,这样看起来就舒服多了
fib2 = @(n) fib(fib, n); 
% Facade(外观模式):为子系统的一组接口提供一个一致的界面。定义一个高层的接口,使得这个子系统更加容易使用,我们的子模块一般都是这么做的。
fib2(4)
fib2(5)
fib2(6)

通用点儿的函数 recur 来将一个函数句柄和其他参数传给自己

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

    % 这个函数的实现原理和上一篇提到的iif类似。如果用这个函数实现那个数列的功能,我们可以这样写,f是函数句柄

fib = @(n) recur(@(f, k) iif(k <= 2, 1, ...  % 递归出口
                           true, @() f(f, k-1) + f(f, k-2)), ... % 不断递归调用
                            n); % 这里n就是recur中的varargin,递归调用后n传给k

% 这里面,
         @(f, k) iif(k <= 2, 1, ...
                          true, @() f(f, k-1) + f(f, k-2)) 
% 对应的是recur函数定义中的 函数句柄 g,这里将g定义成了匿名的菲波纳契函数。
% 通过使用arrayfun,我们可以直接获得数列的前n项
arrayfun(fib, 1:10)

% 另一个例子,级数,也可以类似的实现。 (f(n) = 1 * 2 * 3 * ... n), 注意recur是自己调用自己!!!
factorial = @(n) recur(@(f, k) iif(k == 0, 1, ... % 递归出口
                                   true,   @() k * f(f, k-1)), ...
                                 n); % 这里n就是recur中的varargin,递归调用后n传给k
arrayfun(factorial, 1:7)

为什么要在匿名函数里进行这么复杂的递归操作呢?
首先,类似我们前面提的映射,以及iif,recur函数,可以很好的帮我们理解匿名函数的工作原理。
另外,递归可以帮助我们在匿名函数里实现循环,这一点是最重要的。

5. 辅助函数

我们首先要介绍另一个函数,来帮助我们在匿名函数中执行多条语句。

注意下面这两个函数是有区别的: ()和{}

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

他们让我们可以通过 paren(x, 3, 4)这样的方式来实现x(3, 4) 的功能, 如paren(@plus, 3, 4)。对于 curly ,花括号,也类似。
也就是说,圆括号和花括号在这里被我们定义成了函数。有什么用呢?

比如,我们要写一个函数返回屏幕的宽度和高度,x = get(0, ‘ScreenSize’), 然而,我们不需要前面两个输出,因此我们可以写x(3:4)。
但是如果在匿名函数中调用呢,我们没法把输出保存到一个变量里面!!!!!

那怎么从函数的调用部分直接获得部分输出?其中一种实现方式就是使用 paren 和 curly 函数,这两个函数也和别的语言中实现这一功能语法相似

我们写这样一个函数

screen_size = @() paren(get(0, 'ScreenSize'), 3:4);
screen_size()
% 这样,我们就可以随意索引函数的输出了,例如
magic(3)
paren(magic(3), 1:2, 2:3)
paren(magic(3), 1:2, ':')

对于花括号,也是类似的做法。比如下面这个例子,正则表达式会匹配rain和Spain,但是我们只取第二个输出,

spain = curly(regexp('The rain in Spain....', '\s(\S+ain)', 'tokens'), 2)

% 直接传递冒号也可以,但是在curly函数中,直接传递冒号的时候要加上单引号
[a, b] = curly({'the_letter_a', 'the_letter_b'}, ':')
tmp = {'the_letter_a', 'the_letter_b'}; tmp{:}

执行多条语句
do_three_things() % 没有参数的函数句柄do_three_things后面依次执行三个函数指针,类似委托后面带了3个回调函数
我们通过一行语句执行了三条命令,所有的输出都被存在一个cell数组里面。
前两个输出都是fprintf产生的垃圾,我们希望得到最后一个输出,15,这才是我们要的最大特征值。所以,我们可以借助curly,只取得最后一个值。(下面程序好像并未成功)

% 除了curly, 我们来看看下面一些不同的实现方式. 比如下面这个:
do_three_things = @() curly({fprintf('This is the first thing.\n'), ...
                             fprintf('This is the second thing.\n'), ...
                             max(eig(magic(3)))}, 3);
K>> do_three_things()
This is the first thing.
This is the second thing.
ans = 
    [25]    [26]    [15.000000000000004]

一个复杂一点的例子,例如我们想写一个函数实现下面的功能的句柄:
在屏幕中间显示一张小图像
在图像上画一些随机的点
返回图像窗口和内容

我们可以通过将所有返回值存在一个cell里面,然后用curly来索引我们想要的结果。这及行代码可以通过一个匿名函数实现

dots = @() curly({...   % {} 中括起来的是函数一系列函数指针,或者一系列无返回值的procedure
    figure('Position', [0.5*screen_size() - [100 50], 200, 100], ...
           'MenuBar',  'none'), ...                % Position the figure (the first thing)
    plot(randn(1, 100), randn(1, 100), '.')}, ...  % Plot random points (the second thing)
    ':');                                          % Return everything

[h_figure, h_dots] = dots()

循环与递归

我们上一篇用递归来实现的功能,也可以用一个for循环来实现,例如对于求n的级数,

factorial = 1;
for k = 1:n
   factorial = k * factorial;
end

然而,在匿名函数中,我们无法使用for或者while这样的语句,因此,我们得考虑如何将循环反转为递归实现

要写一个好的循环语句,我们必须知道下面的条件

1.每次循环中要做什么
2.这个过程是否还需要进行下一次的循环
3.循环的起始条件是什么

如果我们“要做什么”定义成一个函数 fcn(x) , 将“是否该继续”定义成另一个函数 cont(x), 然后将“起始条件”定义成一个 x0。这样我们就可以写一个循环函数了。

我们说的再详细一点,

在每一步,我们调用cont函数,传入状态x的所有元素,如 cont(x{:})。
如果结果返回false, 我们不继续,返回当前状态x。
否则,我们调用 fcn 函数,传入状态 fcn(x{:}),然后将所有的输出传入下一次迭代。

将上一段说的这一次循环定义为一个函数 f, 那么我们就可以使用我们的 recur 函数来定义一个匿名的 loop 函数
—- 我以前没看出来下面 数据x0, 函数句柄condition 和 函数句柄fcn 都是传入函数句柄recur的参数 ——-
— iif(~condition(x{:}), x,@() f(f, fcn(x{:}))) 就是recur中的函数句柄f , 注意recur是自己调用自己!!!—-

recur = @(g, varargin) g(g, varargin{:}); 
loop = @(x0, condition, fcn) ...                                             % Header
       recur(@(f, x) iif(~condition(x{:}), x, ...                            % 当 k = n时, ~(k < n)为真, 递归出口
                         true,        @() f(f, fcn(x{:}))), ...              %   recursive invocation
             x0); % 这里x0就是recur中的varargin,递归调用后x0传给x           % from x0. 

% 整个loop函数句柄就是给recur提供一个初值x0; 而整个recur又是给iif提供一个初值x; iif 条件后的都是函数句柄
% 对上面这个简单的例子来说,状态x就是一个循环计数。我们增加计数直到其大于等于n,然后返回最终的循环次数。
% 因此,上面这个函数做的就是从0数到了n。虽然不是很有意思,但是它展示了循环如何来实现
count = @(n) loop({0}, ...          % Initialize state, k, to 0
                  @(k) k < n, ...   % While k < n
                  @(k) {k + 1});    %   k = k + 1 (returned as cell array)

arrayfun(count, 1:10)

看到我们写{0},为什么我们要使用cell来存储状态呢?有两个原因
第一,如果x是一个cell,那么我们将x传入fcn的时候,就可以实现传递多参数。即,fcn(x{:}) 等同于 fcn(x{1}, x{2}, ...)。
第二,这样做就允许了函数返回多个元素,供下一次迭代使用。多个元素将会通过一个cell来返回。例如下面这个级数的例子,我们的状态是两个部分,迭代次数k和上一次计算出的级数x。这两个数字都是我们的输入,
factorial = @(n) loop({1, 1}, ...          % Start with k = 1 and x = 1
                      @(k, x) k <= n, ...  % While k <= n
                      @(k, x) {k + 1, ...  %   k = k + 1;
                               k * x});    %   x = k * x;
factorial(5)
% 函数的返回也是两个,迭代次数和最终的结果。然而,我们可能不想要第一个输出,因为没什么用,那么我们修改一下loop程序,
% 在其中增加了一个 cleanup 函数,当循环执行完,会执行这个函数来去掉不用的输出
loop = @(x0, cont, fcn, cleanup) ...                            % Header
       recur(@(f, x) iif(~cont(x{:}), cleanup(x{:}), ...        % Continue?
                         true,        @() f(f, fcn(x{:}))), ... %   Iterate
             x0); 
% 用新的loop程序实现一下级数功能
factorial = @(n) loop({1, 1}, ...         % Start with k = 1 and x = 1
                      @(k,x) k <= n, ...  % While k <= n
                      @(k,x) {k + 1, ...  %   k = k + 1;
                              k * x}, ... %   x = k * x;
                      @(k,x) x);          % End, returning x, 此行对应cleanup

factorial(5)
% 我们可以通过arrayfun来直接获得多个输入的输出
arrayfun(factorial, 1:7)

不过,必须得承认,这种循环要比用普通的for循环实现来的复杂的多。但是另一方面,这是通过匿名函数实现的,
它在语法上的复杂带来的好处是它具有自己的数据区域,并不会改变循环外任何的变量。
最重要的,也是通过这个例子学习一下匿名函数的高级使用方法。
最终看一个例子,总结一下这三篇帖子,
我们来模拟一个谐振过程。使用一个结构体存储异类状态,包括谐振的完整历史记录。这个可以用来模拟,例如地震时挂在房顶的吊灯摇摆的幅度

% 首先,计算一个状态转移矩阵,用来表示一个有阻尼谐振过程。
% 给其乘以状态|x|,就得到了x在下一时刻的取值。
Phi = expm(0.5*[0 1; -1 -0.2]);

% 创建循环
x   = loop({[1; 0], 1}, ...                  % Initial state, x = [1; 0]
           @(x,k) k <= 100, ...              % While k <= 100
           @(x,k) {[x, Phi * x(:, end)], ... %   Update x
                   k + 1}, ...               %   Update k
           @(x,k) x);                        % End, return x

% 创建画图函数
plot_it = @(n, x, y, t) {subplot(2, 1, n), ...            % Select subplot.
                         plot(x(n, :)), ...               % Plot the data.
                         iif(nargin==4, @() title(t), ... % If there's a
                             true,      []), ...          % title, add it.
                         ylabel(y), ...                   % Label y
                         xlabel('Time (s)')};             % and x axes.

% 绘制结果
plot_it(1, x, 'Position (m)', 'Harmonic Oscillator');
plot_it(2, x, 'Velocity (m/s)');
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值