views:

346

answers:

6

How do I get the number of arguments passed to a function, such as Plus[2,3,4,5] has 4 arguments passed to it. I was thinking it may involve the use of the function Length and getting the arguments into a list. The intention is to iterate an operation based on the number of arguments for a function. There is probably a simple solution or function but I haven't come across it yet. Any other ways or suggestions are welcome as well?

+1  A: 

You can always use lists:

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

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

This way is appropriate to use with Mathematica because list management is very powerful.

If you use f[a,b,c], the number of arguments is hard-coded

But again ... try the functional way.

belisarius
@belisarius: I corrected the LHS of the := expression. I haven't corrected the syntax errors on the RHS. I also observe that if one finds oneself writing loops in Mathematica one has probably strayed from the well-lit path into darkness.
High Performance Mark
@High Performance Mark Thanks a lot. You're right
belisarius
+2  A: 

I think that you are going to have to start intefering with Mathematica's evaluation sequence or, possibly simpler, interfering with the properties of its intrinsic functions. One of the problems you have is that Mathematica evaluates very greedily, so by the time you have pressed return after entering Plus[2,3,4,5] it has done its stuff and returned 14.

You could possibly fiddle with $Pre to achieve what you want. But you might have to Unprotect[Plus] and force it to Hold its arguments until you've had a chance to count how many there are.

Of course, if you were just using Plus as an example and really want to define a function of your own then your task is probably a lot easier. Here is a function I wrote which simply returns the number of arguments it gets:

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

I've tested this on some simple cases. It will be instructive for you to try things like:

fun[1,{2,3}]

I tend to agree with the comment already made, that what you propose to do is not very Mathematica-al

High Performance Mark
@High Performance Mark I understood that the "Plus" was just an example. I would not recommend Playing with intrinsic functions ... nasty results ahead if you are not an expert ...
belisarius
@belisarius: no, I wouldn't recommend altering intrinsic functions, but how else is @dbjohn to become an expert :-)
High Performance Mark
Things get more interesting when you start playing with the `Flat` attribute, too. If `f` is `Flat` (`SetAttributes[f, Flat]`), then `f[1, f[2, 3]]` becomes `f[1, 2, 3]`. It depends on how you want to count things =)
Michael Pilat
Also, `Length` works on any expression, not just `List`. `Length[f[1, 2, 3, 4]]` gives `4`.
Michael Pilat
@Michael Pilat. That would be nice but Length[Plus[1, 2, 3, 4]] gives me zero. ?
dbjohn
@dbjohn: Length[Plus[1,2,3,4]]==0 because Mathematica greedily evaluates the Plus expression to 10, and, quoting from the documentation: Length[expr] returns 0 whenever AtomQ[expr] is True. If you don't understand AtomQ, check the documentation.
High Performance Mark
@dbjohn What HPMark said. You might also be interested in `Unevaluated`, which prevents certain kinds of evaluation for exactly this kind of situation. For example, `Length[Unevaluated[Plus[1, 2, 3, 4]]]` gives `4`.
Michael Pilat
Yes that is effective. By the way does anyone know the difference between Hold and Unevaluated. This Length[Hold[Plus[1, 2, 3, 4]]] gives 1 as a output. I think it may have something to do with levels or upvalues.
dbjohn
`Unevaluated` only prevents evaluation of its argument when given as the argument to another function, but `Hold` prevents the evaluation of its contents at all times. Ergo, what's really being evaluated by `Length[Unevaluated[Plus[1,2,3,4]]]` is `Length[Plus[1,2,3,4]]`, not `Length[10]` as explained above. For a better understanding, I recommend the tutorial on [Non-Standard Evaluation](http://reference.wolfram.com/mathematica/tutorial/NonStandardEvaluation.html). `Hold[Plus[1,2,3,4]]` has a length of `1` because the Hold expression has only one argument.
Michael Pilat
+5  A: 

Here's one way:

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

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

When you define a function like this, the pattern args___ (with 3 trailing underscores) will match a Sequence of 0 or more things. You can't use Length on a Sequence and have anything sensible happen, so you should wrap args in a List (the {}) first.

However, belisarius is correct. For a lot of iterative operations, it will be easier and more efficient to use built-in higher-order functions like Map and Fold.

EDIT to add: Due to way that Mathematica expressions are built on top of bounds-checked arrays, Length is O (1) in time. This might lead you to believe that foo also has O (1) complexity, but you would be wrong. Due to the way pattern-matching works, all of the elements matched by args will be copied into the new List that you then pass to Length, making the complexity O (N). This isn't necessarily a huge problem, because using really huge argument lists with a function almost invariably means using Apply, which does an O (N) copy anyway, but it's something you should know.

EDIT again to add: There's another way to do this using Length directly on the expression being evaluated (like most of Mathematica's list-oriented functions, Length can be used on expressions with any head, not just lists). Nothing is copied because no sequences are matched and given new heads, and the function which is having its arguments counted need not have any special attributes like HoldAll. Nonetheless, it is a sleazy hack that exploits a quirk in the pattern-matching machinery by introducing side-effects where side-effects really don't belong, so I would use it with extreme caution, if at all:

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

The variable n could be global, but Module will create (or at least do a good job faking) lexical closures, so you can at least keep your variables local.

Pillsy
As a point of note, I use the above method of turning a `Sequence` into a `List` to process argument lists, in general. With only a single underscore, it is easy to use, but having a named sequence isn't as easy to use directly within a function.
rcollyer
A: 

Not sure what kind of recursion you need, but in my experience a (Haskel inspired?) first,rest pattern in your function definition can be quite powerful:

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}

And of course, you have access to Length[{rest}] if you need it.

EDIT 2: (Old plot was incorrect as pointed out by Pilsy) Mathematica actually copies the 'rest' part, so that the scaling turns quadratic if the cost of the actual function is negligible.

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]

The figure below shows the output for an inexpensive inner function f[onearg_]:=onearg[[1,1]] as above where the scaling is indeed quadratic in number of arguments, and for an expensive inner function f[onearg_]:=SingularValueList[onearg,1], where the scaling is closer to linear.

output of above code

Janus
!!! Did that figure out that the number of arguments in f[2,3,4] is 22 or did I miss something ?
High Performance Mark
:) -- just wanted to illustrate what I meant by `first, rest` pattern. I guess you could do `f[onearg_]=1` and `f[first_,rest__]:=1+f[rest]` if you wanted the number of arguments calculated in the most opaque way possible?
Janus
This works well in Haskell, where "lists" are really singly-linked lists, and taking the first element and the rest of the elements is an O(1) operation. In Mathematica, however, lists are built on top of arrays. The way the implementation of pattern-matching works, the elements of the Sequence rest in your example will all end up being copied, turning a linear operation into a quadratic one. Don't use this technique in Mathematica.
Pillsy
@Pilsy: I'm afraid I wont buy your dogma.Even if it is an O(n) operation for Mathematica to split the list (I can't believe their array implementation wouldn't support views), the O(n^2) cost would still only apply to the splitting the input list (of pointers). I have a hard time thinking of a situation where this would have any effect on total runtime. Have added a 'benchmark' above, mostly to show that there is at least no deep copying when the input is split.
Janus
@Janus, I'm curious about the spikes in your data (around 400 and 1000). Do they always occur at those places, or are they transient effects of your system you are running on? Of course, if it is persistent, it still could be the hardware you're running on. But, I find irregularities like that interesting.
rcollyer
@rcollyer: Think it's transient load on my 'system' (a dual core notebook) -- if you look at the code, you'll see that I'm averaging the wrong way around: all repeats at a given number of iterations are done right after each other.
Janus
@Janus: you aren't splitting the input in the same way in the benchmark that you're doing there. The first_ and rest_ patterns have single trailing underscores and are only matching single elements; the issue arises with a pattern like rest__ which matches not a single element, but a **sequence** of elements, which invariably gets assigned a new head. I'm not sure why Mathematica doesn't use some kind of slice or view there, but it doesn't.
Pillsy
@Pilsy: Well spotted, Sir! It must have been calling another `f` -- if only I had a keyboard shortcut to quit the kernel.New (hopefully correct) timings agree with you on the quadratic scaling, as you can see. Still, I wont stop using this pattern since the total timing is still linear if the main cost is in the inner function (as illustrated in the right part of the figure).
Janus
+1  A: 

Per my comments in another answer, the idiomatic way to do this is typically:

Length[Unevaluated[expr]]

E.g.:

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

Out[1]= 4

The use of Unevaluated prevents the argument from evaluating, avoiding the situation where the argument to Length (which does not have any Hold* attributes) would evaluate to an atomic value (like a number) which doesn't have a length, and Length returns 0 in such cases:

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

Out[2]= 0
Michael Pilat
A: 

Some of the solutions above require that you input the arguments explicitly into a function which puts it in a list. With my own study I have found there is way to calculate the answer. Given the expression 3 + 2*2*2*4 + 5, find how many arguments are passed to the Times function. With a bit of help visualising it with the TreeForm function, I put together some of mathematica's built in functions to evaluate an answer.

Steps:
1/ Get position of the function.
2/ It returns a nested list.
3/ Flatten list.
4/ Get list's length which will be the level that the arguments of the function are at.
5/ Level returns a list of the arguments which you can then get the length of.

Example:

In[89]:= Position[Hold[3 + 2*2*2*4 + 5], Times]

Out[89]= (1    2    0)

In[90]:= FullForm[%]

Out[90]= FullForm= List[List[1,2,0]]

In[91]:= Flatten[%]

Out[91]= {1,2,0}

In[92]:= FullForm[%]

Out[92]= FullForm= 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

This could all be put in a function. Though it may not handle the case when there are two instances of the same function within an expression in an automated way. This may require some condition set or input by the user.

dbjohn