【问题标题】:Mathematica: get number of arguments passed to a function?Mathematica:获取传递给函数的参数数量?
【发布时间】:2011-03-04 02:46:00
【问题描述】:

如何获取传递给函数的参数数量,例如Plus[2,3,4,5] 有 4 个参数传递给它。我在想它可能涉及使用函数 Length 并将参数放入列表中。目的是根据函数的参数数量迭代操作。可能有一个简单的解决方案或功能,但我还没有遇到过。也欢迎其他方式或建议?

【问题讨论】:

  • “通常”Mathematica 最适合“功能”而不是“迭代”方法

标签: function count arguments wolfram-mathematica


【解决方案1】:

这是一种方法:

In[1]:= foo[args___] := Length[{args}]

In[2]:= foo[1,2,3,4]
Out[2]= 4

当您定义这样的函数时,模式 args___(带有 3 个尾随下划线)将匹配 Sequence 的 0 个或更多事物。你不能在Sequence 上使用Length 并且有任何合理的事情发生,所以你应该首先将args 包裹在List{})中。

然而,贝利撒留是正确的。对于很多迭代操作来说,使用MapFold等内置的高阶函数会更容易、更高效。

编辑添加:由于 Mathematica 表达式是建立在边界检查数组之上的,Length 在时间上是 O (1)。这可能会让你相信 foo also 具有 O (1) 复杂性,但你错了。由于模式匹配的工作方式,args 匹配的所有元素都将复制到新的List 中,然后您将其传递给Length,从而使复杂性O N)。这不一定是个大问题,因为在函数中使用非常大的参数列表几乎总是意味着使用Apply,它无论如何都会进行O (N) 复制,但这是你应该知道的。

再次编辑以添加:还有另一种方法可以直接在被评估的表达式上使用Length(就像大多数 Mathematica 的面向列表的函数一样,Length 可以用于表达式任何头部,而不仅仅是列表)。没有任何内容被复制,因为没有序列被匹配并被赋予新的头,并且正在计算其参数的函数不需要像HoldAll这样的任何特殊属性。尽管如此,这是一个低俗的黑客攻击,它通过引入副作用真正不属于的副作用来利用模式匹配机制中的一个怪癖,所以我会非常谨慎地使用它,如果有的话:

Module[{n},
 expr : foo[___] /; (n = Length[Unevaluated[expr]]; True) :=
  n]

变量n 可以是全局变量,但Module 会创建(或至少做好伪装)词法闭包,因此您至少可以将变量保持在本地。

【讨论】:

  • 请注意,我通常使用上述将Sequence 转换为List 的方法来处理参数列表。只有一个下划线,很容易使用,但是有一个命名的序列并不那么容易在函数中直接使用。
  • 这是我在比较使用 {x} 与 Unevaluated[x] (在下面的解决方案中使用)时注意到的一个有趣的效果。定义F[x___,A,y___]:=Length[{x}],即你只想知道固定参数A出现在你的F中的哪个位置。试试F[B],F[A],F[B ,A],F[B,B,A]。给出 F[B],0,1,2。完美的。重复未评估。笏。 :-)
【解决方案2】:

我认为您将不得不开始干扰 Mathematica 的评估序列,或者可能更简单,干扰其内在函数的属性。您遇到的问题之一是 Mathematica 的计算非常贪婪,所以当您在输入 Plus[2,3,4,5] 后按回车键时,它已经完成了它的工作并返回了 14。

您可以摆弄$Pre 来实现您想要的。但是您可能必须 Unprotect[Plus] 并强制它使用 Hold 的参数,直到您有机会计算有多少。

当然,如果您只是以Plus 为例,并且真的想定义自己的函数,那么您的任务可能会容易得多。这是我编写的一个函数,它只返回它获得的参数数量:

fun[y___]:=Length[{y}]

我已经在一些简单的情况下对此进行了测试。尝试以下方法对您很有指导意义:

fun[1,{2,3}]

我倾向于同意已经发表的评论,即您建议做的不是很Mathematica-al

【讨论】:

  • @High Performance Mark 我知道“Plus”只是一个例子。我不建议你玩内在函数……如果你不是专家的话,结果会很糟糕……
  • @belisarius:不,我不建议改变内在函数,但@dbjohn 还能如何成为专家 :-)
  • 当您开始使用Flat 属性时,事情也会变得更有趣。如果fFlat (SetAttributes[f, Flat]),则f[1, f[2, 3]] 变为f[1, 2, 3]。这取决于您要如何计算事物=)
  • 此外,Length 适用于任何表达式,而不仅仅是 ListLength[f[1, 2, 3, 4]]4
  • Unevaluated 仅在将其作为参数提供给另一个函数时阻止对其参数进行评估,但Hold 始终阻止对其内容进行评估。因此,Length[Unevaluated[Plus[1,2,3,4]]] 真正评估的是Length[Plus[1,2,3,4]],而不是如上所述的Length[10]。为了更好地理解,我推荐Non-Standard Evaluation上的教程。 Hold[Plus[1,2,3,4]] 的长度为 1,因为 Hold 表达式只有一个参数。
【解决方案3】:

根据我的 cmets 在另一个答案中,这样做的惯用方式通常是:

Length[Unevaluated[expr]]

例如:

In[1]:= Length[Unevaluated[Plus[1, 2, 3, 4]]]

Out[1]= 4

Unevaluated 的使用阻止了参数的计算,避免了 Length 的参数(没有任何 Hold* 属性)将计算为原子值(如数字)的情况t 有一个长度,Length 在这种情况下返回0

In[2]:= Length[Plus[1, 2, 3, 4]]

Out[2]= 0

【讨论】:

    【解决方案4】:

    你总是可以使用列表:

    f[list_]:= (len = Length[list];
                While [....
                       do whatever
                     ];
                Return [ ..];
               );
    
     myOut= f[{a,b,c}];
    

    这种方式适合与 Mathematica 一起使用,因为列表管理非常强大。

    如果使用 f[a,b,c],参数的数量是硬编码的

    但是再次...尝试功能性方式。

    【讨论】:

    • 我更正了 := 表达式的 LHS。我还没有更正 RHS 上的语法错误。我还观察到,如果一个人发现自己在 Mathematica 中编写循环,那么他可能已经偏离了光线充足的路径,进入了黑暗。
    【解决方案5】:

    不确定您需要哪种递归,但根据我的经验(受 Haskel 启发?)您的函数定义中的first,rest 模式可能非常强大:

    f[onearg_]:=onearg
    f[first_,rest__]:=first+2 f[rest]
    
    In[148]= Trace@f[2,3,4]
    Out[148]= {f[2,3,4],2+2 f[3,4],{{f[3,4],3+2 f[4],{{f[4],4},2 4,8},3+8,11},2 11,22},2+22,24}
    

    当然,如果需要,您可以访问Length[{rest}]

    编辑 2:(Pilsy 指出,旧图不正确)Mathematica 实际上复制了“其余”部分,因此如果实际函数的成本可以忽略不计,则缩放变为二次方。

    f[] := 0;
    f[onearg_] := onearg[[1, 1]];
    f[first_, rest__] := f[first] + f[rest];
    ListLogLogPlot[Part[#, -1, 1],
       Joined -> True, PlotRange -> {{100, All}, Automatic}, 
       AxesLabel -> {"#Arguments", "Runtime"}] &@
     Reap@Nest[
       Function[all, 
        Sow[{Length[all], First@Timing[Table[f @@ all, {10}]]}];
        Join[all, RandomReal[{-1, 1}, {10, 10, 10}]]], {}, 100]
    

    下图显示了上面的廉价内部函数f[onearg_]:=onearg[[1,1]] 的输出,其中缩放在参数数量上确实是二次的,而对于昂贵的内部函数f[onearg_]:=SingularValueList[onearg,1],缩放更接近于线性。

    【讨论】:

    • !!!这是否发现 f[2,3,4] 中的参数数量为 22 或者我错过了什么?
    • :) -- 只是想说明我所说的first, rest 模式是什么意思。如果您希望以最不透明的方式计算参数的数量,我想您可以使用 f[onearg_]=1f[first_,rest__]:=1+f[rest]
    • 这在 Haskell 中运行良好,其中“列表”实际上是单链表,获取第一个元素和其余元素是 O(1) 操作。然而,在 Mathematica 中,列表是建立在数组之上的。模式匹配的实现方式,您的示例中剩余的序列元素最终都将被复制,将线性运算变成二次运算。不要在 Mathematica 中使用这种技术。
    • @Pilsy:恐怕我不会相信你的教条。即使 Mathematica 拆分列表是 O(n) 操作(我不敢相信他们的数组实现不支持视图),O(n^2) 成本仍然只适用于拆分输入列表(指针)。我很难想到这会对总运行时间产生任何影响的情况。在上面添加了一个“基准”,主要是为了表明在拆分输入时至少没有深度复制。
    • @Pilsy:很好看,先生!它一定是在调用另一个f——如果我有一个键盘快捷键来退出内核就好了。如您所见,新的(希望是正确的)时间在二次缩放方面与您一致。不过,我不会停止使用这种模式,因为如果主要成本在内部函数中,总时间仍然是线性的(如图右侧所示)。
    【解决方案6】:

    上面的一些解决方案要求您将参数显式输入到将其放入列表的函数中。通过我自己的研究,我发现有办法计算答案。给定表达式3 + 2*2*2*4 + 5,找出传递给Times 函数的参数数量。在使用TreeForm 函数对其进行可视化的帮助下,我将一些mathematica 的内置函数放在一起来评估答案。

    步骤:
    1/ 获取函数的位置。
    2/ 它返回一个嵌套列表。
    3/ 扁平化列表。
    4/ 获取列表的长度,这将是函数参数所在的级别。
    5/ Level 返回参数列表,然后您可以获取其长度。

    例子:

    In[89]:= Position[Hold[3 + 2*2*2*4 + 5], Times]
    
    Out[89]= (1    2    0)
    
    In[90]:= FullForm[%]
    
    Out[90]= List[List[1,2,0]]
    
    In[91]:= Flatten[%]
    
    Out[91]= {1,2,0}
    
    In[92]:= FullForm[%]
    
    Out[92]= List[1,2,0]
    
    In[93]:= Length[%]
    
    Out[93]= 3
    
    In[94]:= Level[Hold[3 + 2*2*2*4 + 5], {%}]
    
    Out[94]= {2,2,2,4}
    
    In[95]:= Length[%]
    
    Out[95]= 4
    

    这都可以放在一个函数中。 尽管它可能无法自动处理表达式中同一函数的两个实例的情况。这可能需要用户设置或输入一些条件。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-04-02
      • 2015-01-05
      • 2017-10-16
      • 1970-01-01
      • 2016-05-23
      • 2012-05-30
      • 1970-01-01
      • 2013-01-27
      相关资源
      最近更新 更多