为迭代函数组合定义泛型函数

Defining a generic function for iterated function composition

让我们定义任何函数句柄foo:

foo = @(x) x*2

我正在尝试编写一个生成函数 foon-th functional power 的通用函数 defFun,即 n 的迭代调用foo,在某种程度上它可以存储在另一个句柄函数中 boo,像这样:

boo = defFun(foo,n)

例如,

foo = @(x) x^2;  
boo = defFun(foo,3);

boo(3) 会给出 6561 [== foo(foo(foo(3)))]boo(2) 会给出 256 [== foo(foo(foo(2)))].

我尝试用这段代码编写 defFun,但这些句柄很难处理。有什么想法吗?

function boo = defFun(foo,n)
   h = foo;
   for i=2:n
      h = h(h);
   end
   boo = h
end

你只是把你的变量搞混了一点。您的问题是您将函数句柄而不是值提供给匿名函数。

这会起作用,以您的示例为基础。

foo = @(x) x^2;

function boo =  defFun(fcn_handle,n)

   v = fcn_handle(n); % The output here is the value of the anonymous function
   for i=2:n
     v = fcn_handle(v); 
   end
   boo = v;
end

defFun(foo,3)

ans =  6561

如果您的函数句柄 foo 仅包含数学公式(如您的示例中的情况),您可以使用 MATLAB 符号工具箱来计算 boo[=15= 的公式]

function boo = defFun(foo,n)
    syms x;                    % Create a symbolic variable x
    f = sym(foo);              % Convert the function handle to a symbolic expression
    g = symfun(f,x);           % Create a symbolic function to work with

    v = g;
    for k=2:n                  % repeat n times
        v = g(v);
    end

    boo = matlabFunction(v);   % convert v to a function handle
end

在您 n=3 的示例中,这将创建函数 handle

foo = @(x) x.^2;
boo = defFun(foo,3)
boo = 
    @(x)x.^8

boo(3)
ans =
    6561

这里的 3 种解决方案依赖于构建一个字符串,然后使用 str2func 从中获取函数句柄。相同功能的不同实现,但结果的可读性不同。

请注意,正如评论中突出显示的那样(感谢 knedlsepp),递归顺序 n 不能超过 32


一种方法是解析输入函数字符串定义并在将其转换为函数句柄之前在字符串中递归地重新创建它:

function boo = defFun(foo,n)

   %% // retrieve the string of the initial function
   A = functions(foo) ;
   fstrfull = A.function ;

   %% // find "input variables" in the header
   [i1 i2] = regexp(fstrfull, '\(([^\)]+)\)' , 'once' ) ;   %// probably can be improved, regexp are not my strong suit
   strVar = fstrfull(i1+1:i2-1) ;  %// =>  strVar='x'       %// to get rid of the parenthesis returned by the regex

   %% // isolate only the function expression (without the header)
   ilast = strfind( fstrfull , ')' )+1 ;  %// ilast= 5      find the last position of the header
   fstr = fstrfull(ilast(1):end) ;        %// fstr='x.^2'   separate only function expression

   %% // replace "variables" by the expression the desired number of time
   strFinalFunction = fstr ;
   for i=2:n
      strFinalFunction = strrep(strFinalFunction, strVar, ['(' fstr ')'] ) ;
   end
   boo = str2func( ['@(' strVar ')' strFinalFunction ] ) ;

end

这会给你:

>> boo = defFun(foo,3)
boo = 
    @(x)((x.^2).^2).^2 // <= your function shows the full expression

>> boo(3)
ans =
        6561

只要输入函数只将一个变量作为输入,它将适用于更复杂的情况。


或者,还有一个更简单的方法应该更通用。它不需要解析,因此将在上述解决方案中的解析失败的潜在情况下工作。缺点是函数定义变得非常不透明。

function boo = defFun2(foo,n)

cfoo = {foo} ; %// place the function handle in a cell

%// create a string calling the function on itself N number of times
strFun = ['@(x) ' repmat('cfoo{1}(',1,n) 'x' repmat(')',1,n) ] ;

%// Generate a function handle for the corresponding function
boo = str2func( strFun ) ;

但现在您的函数定义如下所示:

>> boo = defFun2(foo,3)
boo = 
    @(x)cfoo{1}(cfoo{1}(cfoo{1}(x))) // <= your function does not show what it does (only the number of time it calls itself)

可读性差很多,但它仍然给出正确的结果。


最后,如果可读性很重要,您还可以在函数定义中包含原始函数的名称,但您将不得不求助于有争议的 eval

function boo = defFun3(fh,n)

fname = inputname(1) ;        %// get the name of the function which was called
eval( [ fname '={fh};' ] ) ;  %// place the function handle in a cell

strFun = ['@(x) ' repmat([fname '{1}('],1,n) 'x' repmat(')',1,n) ] ; %// create a string calling the function on itself N number of times
boo = str2func( strFun ) ;                                           %// Generate a function handle for the corresponding function

这现在为您提供:

boo = defFun3(foo,3)
boo = 
    @(x)foo{1}(foo{1}(foo{1}(x))) // <= now you know that boo is the function 'foo' called on itself 3 times.

我的代码与您的原始代码非常相似。我冒昧地重命名了一些变量。

直接方法:

对于单输入和单输出参数,您可以使用类似于您的代码的直接方法:

function ftoN = fIterate(f, N)
    ftoN = f;
    for i = 2:N
        ftoN = @(x) f(ftoN(x)); 
    end
end

间接方法:(可能更快)

这个会快很多,它也适用于多个(但相同数量的)输入和输出。

function ftoN = fIterate(f, N)
    ftoN = @(varargin)   fIterateLocal(f, N, varargin{:});
    function varargout = fIterateLocal(f, N, varargin)
        varargout = varargin;
        for i = 1:N
            [varargout{1:nargin-2}] = f(varargout{:});
        end
    end
end

示例:

两种方法都适用于此:

square = @(x) x^2;
square(2)
>> ans = 4

squaresquaresquare = fIterate(square, 3)
squaresquaresquare(3)
>> ans = 6561

后果:

直接方法会比较慢,而且受到 MATLAB Maximum recursion limit 的限制。

timeit(@()feval(fIterate(@(X)X+1,400),0))
ans = 1.2160

间接方法会给你更多的速度和灵活性:

timeit(@()feval(fIterate(@(X)X+1,400),0))
ans = 0.0072