Mathematica:如何获取传递给函数的参数数量?

6

如何获取传递给函数的参数数量,例如Plus[2,3,4,5]有4个参数传递给它。我认为这可能涉及使用函数Length并将参数放入列表中。目的是根据函数的参数数量迭代操作。可能存在简单的解决方案或函数,但我尚未遇到它。欢迎提出任何其他方法或建议。


1
通常情况下,Mathematica 更适合于“函数式”而不是“迭代式”的方法。 - Dr. belisarius
6个回答

7

这里有一种方法:

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

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

当你定义一个函数时,模式args___(带有3个下划线)将匹配0个或多个东西的Sequence。您无法在Sequence上使用Length并获得任何明智的结果,因此您应该首先将args包装在List(即{})中。
然而,belisarius是正确的。对于许多迭代操作,使用内置的高阶函数如MapFold会更容易和更有效。 编辑以添加: 由于 Mathematica 表达式是建立在经过边界检查的数组之上的,因此 Length 的时间复杂度为 O (1)。这可能会让你认为 foo 的复杂度也是 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”的方法来处理参数列表。只有一个下划线时很容易使用,但在函数内直接使用命名序列并不那么容易。 - rcollyer
这里有一个有趣的效果,当比较使用{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。完美。用Unevaluated重复。咦? :-) - Hauke Reddmann

2
我认为你需要开始干涉Mathematica的计算顺序,或者更简单地干涉其内在函数的属性。你面临的问题之一是,Mathematica非常贪婪地进行计算,因此当你输入Plus [2,3,4,5]并按下回车键时,它已经完成了计算并返回了14。
你可以通过调整$Pre来实现你想要的效果。但你可能需要Unprotect[Plus]并强制它保留其参数,直到你有机会数出有多少个参数。
当然,如果你只是将Plus用作示例,并真正想定义自己的函数,那么你的任务可能会容易得多。以下是我编写的一个函数,它只返回它接收到的参数数量:
fun[y___]:=Length[{y}]

我已经在一些简单的情况下进行了测试。你可以尝试以下操作,这将对你有所帮助:

fun[1,{2,3}]

我倾向于同意之前评论所说的,你所提出的做法不是很"Mathematica"化。

@belisarius:不,我不会建议修改内置函数,但是@dbjohn怎么才能成为专家呢 :-) - High Performance Mark
当你开始玩 Flat 属性时,事情变得更有趣了。如果 fFlat (SetAttributes[f, Flat]),那么 f[1, f[2, 3]] 就会变成 f[1, 2, 3]。这取决于你想如何计算。=) - Michael Pilat
@dbjohn,HPMark的意见很棒。你可能还会对Unevaluated感兴趣,它可以防止特定类型的计算,以应对这种情况。例如,Length[Unevaluated[Plus[1,2,3,4]]]会输出4 - Michael Pilat
是的,那很有效。顺便问一下,有人知道Hold和Unevaluated之间的区别吗?这个Length[Hold[Plus[1, 2, 3, 4]]]输出1。我认为这可能与级别或上值有关。 - dbjohn
1
Unevaluated 只有在作为另一个函数的参数时才防止其参数的求值,但 Hold 则始终防止其内容的求值。因此,Length[Unevaluated[Plus[1,2,3,4]]] 实际上计算的是 Length[Plus[1,2,3,4]],而不是如上所述的 Length[10]。为了更好地理解,建议参考 非标准求值 教程。Hold[Plus[1,2,3,4]] 的长度为 1,因为 Hold 表达式只有一个参数。 - Michael Pilat
显示剩余4条评论

1

你总是可以使用列表:

f[list_]:= (len = Length[list];
            While [....
                   do whatever
                 ];
            Return [ ..];
           );

 myOut= f[{a,b,c}];

这种方法适用于Mathematica,因为列表管理非常强大。

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

但再次尝试函数式方法。


1
我已经纠正了 := 表达式的左侧。我还没有纠正 RHS 上的语法错误。我还观察到,如果一个人发现自己在 Mathematica 中编写循环,那么他可能已经偏离了明亮之路,进入了黑暗之境。 - High Performance Mark

1

根据我在另一个答案中的评论,通常做法是使用惯用语:

Length[Unevaluated[expr]]

E.g.:

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

Out[1]= 4

Unevaluated 的使用可以防止参数被计算,避免了传递给 Length(没有任何 Hold* 属性)的参数被计算为原子值(如数字),这种情况下该值没有长度,Length 在这种情况下返回 0

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

Out[2]= 0

0

不确定您需要哪种递归,但根据我的经验,在函数定义中使用(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会复制'rest'部分,因此如果实际函数的成本可以忽略不计,则缩放会变成二次方。
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],其中缩放更接近于线性。

output of above code


这在Haskell中效果很好,因为“列表”实际上是单链表,在其中获取第一个元素和其余元素是O(1)操作。然而,在Mathematica中,列表是建立在数组之上的。模式匹配的实现方式使得您示例中Sequence的元素将全部被复制,从而将线性操作转换为二次操作。不要在Mathematica中使用此技术。 - Pillsy
1
@Pilsy:恐怕我不会接受你的教条主义观点。即使Mathematica将列表拆分为O(n)操作(我无法相信他们的数组实现不支持视图),O(n^2)成本仍然只适用于拆分输入列表(指针)。我很难想象这会对总运行时间产生任何影响的情况。已添加一个“基准测试”,主要是为了表明在拆分输入时没有进行深层复制。 - Janus
@Janus,我对你数据中的峰值(大约在400和1000处)很好奇。它们总是发生在那些地方吗?还是你运行的系统的瞬态效应?当然,如果它是持久性的,仍然可能是你正在运行的硬件问题。但是,我觉得这样的不规则性很有趣。 - rcollyer
@rcollyer:我觉得这是我的“系统”上的瞬态负载(双核笔记本电脑)——如果你看一下代码,你会发现我平均计算的方式错了:所有在给定迭代次数下的重复计算都会紧接着进行。 - Janus
1
@Pilsy:很敏锐的发现,先生!它一定是在调用另一个f——如果我有一个退出内核的键盘快捷键就好了。 新的(希望正确的)时间与您在二次缩放方面达成共识,正如您所见。尽管如此,如果主要成本在内部函数中(如图的右侧所示),总计时仍然是线性的,因此我不会停止使用这种模式。 - Janus
显示剩余3条评论

0

以上一些解决方案需要您将参数明确输入到一个函数中,然后将其放入列表中。通过自己的研究,我发现有一种方法可以计算答案。给定表达式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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接