注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Mr.Right

不顾一切的去想,于是我们有了梦想。脚踏实地的去做,于是梦想成了现实。

 
 
 

日志

 
 
关于我

人生一年又一年,只要每年都有所积累,有所成长,都有那么一次自己认为满意的花开时刻就好。即使一时不顺,也要敞开胸怀。生命的荣枯并不是简单的重复,一时的得失不是成败的尺度。花开不是荣耀,而是一个美丽的结束,花谢也不是耻辱,而是一个低调的开始。

网易考拉推荐

阿英讲matlab匿名函数传入函数指针,varargin  

2013-04-14 12:36:52|  分类: 编程 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
即以此功德,庄严佛净土。上报四重恩,下济三途苦。惟愿见闻者,悉发菩提心。在世富贵全,往生极乐国。

缘起:这篇文章是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)');
  评论这张
 
阅读(1214)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2016