- 积分
- 57
- 注册时间
- 2006-1-10
- 仿真币
-
- 最后登录
- 1970-1-1
|
本帖最后由 rocwoods 于 2014-1-21 15:29 编辑
http://blogs.mathworks.com/loren ... s-functions-part-1/
http://blogs.mathworks.com/loren ... s-functions-part-2/
http://blogs.mathworks.com/loren ... s-functions-part-3/
查找资料时还发现了中文翻译注解版,原帖链接:
http://anony3721.blog.163.com/blog/static/511974201331402843580/
对翻译并注解的作者表示感谢!,方便大家阅读贴出来:
阿英讲matlab匿名函数传入函数指针,varargin 2013-04-14 12:36:52| 分类: 编程 | 标签:matlab 匿名函数 |举报|字号 订阅
即以此功德,庄严佛净土。上报四重恩,下济三途苦。惟愿见闻者,悉发菩提心。在世富贵全,往生极乐国。
缘起:这篇文章是matlab博客里叫Loren的一个作者写的,里面讲了匿名函数的高级用法,写的很不错,看了很受益,所以贴出来希望对大家有帮助。文章和代码是前天花了一晚上,昨天花了一上午调试,注释的。由于是高级编程技巧,在没有学佛以前我是很自私的,大家也不可能看到这篇文章;目前,大家能看到的原因是本人有幸听闻佛陀正法,我秉承佛菩萨,自利利他,自觉觉他,心量广大,无贪嗔痴,上求佛道,下化众生的的宗旨指导自己的行为,言语,思想。 学佛,就是要比别人强,比别人更懂得感恩,比别人更珍惜时间,比别人的生命更有价值,比别人心量大,比别人有智慧,比别人人品高尚,比别人早觉悟,南无阿弥陀佛!- %匿名函数的优点在于使用方便,不需要保存成一个单独的文件。
- %对于一些只使用一次,并且结构简单的函数来说,
- %这样做可以避免整个工程的文件系统过于混乱。
- %% 最大最小值函数
- % 我们来写一个函数,找到数组中的最大和最小值并且将其存在另一个数组里。如下,
- % ------- 我以前不知道如何用匿名函数返回两个以上的返回值 ----------
- min_and_max = @(x) [min(x), max(x)];
- min_and_max([3 4 1 6 2])
- min_and_max = @(x) {min(x), max(x)}; % another version
- celldisp(min_and_max([3 4 1 6 2])) % min_and_max后面依次执行2个函数指针,类似c#委托后面带了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)就是函数指针,这个匿名函数的参数也是函数指针-------
- % ---- 我以前不会这样将函数名作为参数的函数调用,C#中的delegate就是以函数名为参数的 -------------------------
- min_and_max = @(x) cellfun(@(f) f(x), {@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)
- %% 映射
- % 我们定义val为一个cell数组,里面包含了我们的输入数据,fcns是一系列函数句柄
- % ------ 我以前没想到传递函数指针给匿名函数@(f) f(val{:})是这么爽 -------
- map = @(val, fcns) cellfun(@(f) f(val{:}), fcns); % cellfun以函数指针为参数时有点像C#中的委托delegate
- % 这个写法有什么用呢?我们可以通过这种方式改写一下上面的min_and_max函数,让它变得更短一些。我们现在要将x通过max和min函数进行映射
- x = [3 4 1 6 2];
- [extrema, indices] = map({x}, {@min, @max})
- map({1, 2}, {@plus, @minus, @times})
- % --- 下面的例子的目的:给函数传入数据的同时传入操作这些数据的函数指针,真是爽 -------------
- mapc = @(val, fcns) cellfun(@(f) f(val{:}), fcns, 'UniformOutput', false);
- % 我们做个实验,将pi送到多个函数里面,输出完全不兼容的结果,包括数字和字符串
- mapc({pi}, {@(x) 2 * x, ... % Multiply by 2
- @cos, ... % Find cosine
- @(x) sprintf('x is %.5f...', x)}) % Return a string
- % 用了这个mapc,一个顶过去N个,一行代码搞定很多问题,简洁。
- %% 内嵌条件语句
- % 有时候我们也许想在匿名函数里面使用if else之类的语句。然而,普通的Matlab语法并不允许在匿名函数中写入这样的句子。
- % 为了解决这一问题,我们单独写一个“inline if”这样的内嵌条件函数,
- iif = @(varargin) varargin{2 * find([varargin{1:2:end}], 1, 'first')}(); %是一个很好用的条件判断函数
- % 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) );
- %测试一下
- normalize([1 1 0])
- % 如果有inf值呢,
- try
- normalize([0 inf 2]), catch err, disp(err.message);
- end
- % 全0的情况
- normalize([0 0 0])
- % 虽然这个函数完全不必要用匿名函数这样写,但是这个例子说明了 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函数,
- % 可以很好的帮我们理解匿名函数的工作原理。另外,递归可以帮助我们在匿名函数里实现循环,这一点是最重要的。
- % 这个我们将在下一篇里介绍。这里,我们首先要介绍另一个函数,来帮助我们在匿名函数中执行多条语句。
- %% 辅助函数
- % 这个小函数在很多地方都会很有用,我们以后会经常用到 curly . 注意下面这两个函数是有区别的,
- 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{:}
- %% 执行多条语句
- % 除了curly, 我们来看看下面一些不同的实现方式. 比如下面这个:
- do_three_things = @() {fprintf('This is the first thing.\n'), ...
- fprintf('This is the second thing.\n'), ...
- max(eig(magic(3)))};
- do_three_things() % 没有参数的函数句柄do_three_things后面依次执行三个函数指针,类似委托后面带了3个回调函数
- % 我们通过一行语句执行了三条命令,所有的输出都被存在一个cell数组里面。
- % 前两个输出都是fprintf产生的垃圾,我们希望得到最后一个输出,15,这才是我们要的最大特征值。所以,我们可以借助curly,只取得最后一个值。
- do_three_things = @() curly({fprintf('This is the first thing.\n'), ...
- fprintf('This is the second thing.\n'), ...
- max(eig(magic(3)))}, 3);
- do_three_things()
- % %一个复杂一点的例子,例如我们想写一个函数实现下面的功能
- %
- % 在屏幕中间现实一张小图像
- % 在图像上画一些随机的点
- % 返回图像窗口和内容的句柄
- % 我们可以通过将所有返回值存在一个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是自己调用自己!!!----
- 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)');
复制代码
|
评分
-
2
查看全部评分
-
|