Can a function be made to accept a variable amount of inputs?

39

23

I have a function that takes two inputs and processes them for a single output. What I need is one that can take a varying number of inputs. and process them to a single output. Is this possible in Mathematica?

 DatasetAverage[inputData_, inputData2_] := Block[{dataAvg, v, w},
   v = Length[inputData];
   w = Length[inputData2];
   If [v != w,
    Print["DatasetAverage: Data sample sizes do not match"]];
   dataAvg = Table[Mean[{inputData[[i]], inputData2[[i]]}], {i, 1, Length[inputData2]}]
 ]

R Hall

Posted 2012-06-09T01:18:26.203

Reputation: 4 190

1You may want to wait for other answers before accepting. I am sure the regulars here can offer more refined answers. – Michael Wijaya – 2012-06-09T01:44:09.383

1

I discussed multiple-arg functions briefly in my book, which may add a little more info to the excellent answers you received here.

– Leonid Shifrin – 2012-06-09T09:58:34.680

thanks @Leonid Shifrin I will review it! – R Hall – 2012-06-10T17:02:43.090

Answers

46

Yes, for both named patterns and pure functions.

Pure functions

You can see that inherently they accept multiple arguments but discard those that are not used:

{#} &[1, 2, 3]  (* out: {1} *)

The object ##, (SlotSequence), represents all arguments wrapped in Sequence, e.g. Sequence[1, 2, 3]. (Internally it doesn't use Sequence but it behaves similarly in most places.)

{##} &[1, 2, 3]  (* out: {1, 2, 3} *)

You can combine # and ##, including their numbered forms:

{"first" -> #, "rest" -> {##2}, "all" -> {##}} &[1, 2, 3]
{"first" -> 1, "rest" -> {2, 3}, "all" -> {1, 2, 3}}

Named patterns

Using Blank*:

_ (Blank)
__ (BlankSequence)
___ (BlankNullSequence):

f[a_, b__] := {"first" -> a, "rest" -> {b}}

f[1, 2, 3] (* out: {"first" -> 1, "rest" -> {2, 3}} *)

__ requires an argument to be present while ___ does not:

f[1] (* out: f[1] *)

g[a_, b___] := {"first" -> a, "rest" -> {b}}

g[1] (* out: {"first" -> 1, "rest" -> {}} *)

Multiple variable length named patterns can be given and will by default be matched shortest first:

h[a_, b__, c__] := {"a" -> a, "b" -> {b}, "c" -> {c}}

h[1, 2, 3, 4, 5]  (* out: {"a" -> 1, "b" -> {2}, "c" -> {3, 4, 5}} *)

This can be controlled with Shortest and Longest:

i[a_, Longest[b__], c__] := {"a" -> a, "b" -> {b}, "c" -> {c}}

i[1, 2, 3, 4, 5] (* out: {"a" -> 1, "b" -> {2, 3, 4}, "c" -> {5}} *)

See this answer for an advanced use of these functions.

The Blank* functions are not the only way to create a variable length pattern. You can also use Repeated (..) or RepeatedNull (...):

j[x : _Real ..] := {x}

j[1.1, 1.2, 1.3]  (* out: {1.1, 1.2, 1.3} *)

These methods can be used in powerful ways such as destructuring.

Optional arguments

In addition to the variable length methods above one can make use of Optional parameters with or without the use of Default values. A basic example:

k[a_, b_: 3, c_: 5] := {a, b, c}

k[1]
k[1, 2]
k[1, 2, 3]
{1, 3, 5}

{1, 2, 5}

{1, 2, 3}

In the example above there are two optional parameters. By default they are filled in sequential order, meaning that in k[1, 2] the 2 is bound to b. This also can be controlled with Shortest and Longest as noted in the section above.

In a limited way optional arguments can also be used for pure functions:

Default can be used to set the default values for a given function, rather than specifying them as part of each function definition, but it must be used before they are defined, and it cannot be used to change them for existing definitions. (Why does Default behave like this?)

Default[m, 1] = 1;
Default[m, 2] = 3;
Default[m, 3] = 5;

m[a_., b_., c_.] := {a, b, c}

m[]
m[2]
m[2, 4]
m[2, 4, 6]
{1, 3, 5}

{2, 3, 5}

{2, 4, 5}

{2, 4, 6}

Also see:

Related Q&A's of a more advanced nature:

Mr.Wizard

Posted 2012-06-09T01:18:26.203

Reputation: 259 163

@Mr.Wizard, how would you call on the last argument using Pure functions? The context I have in mind is that I am going to display the product of a variable number of matrices and I want to refer to the last matrix in my code. It looks like Length[{##}] will give me the number of inputs but I am not sure how to access ##n where n is this number of inputs. – Ben Allgeier – 2014-07-31T05:05:54.250

@BenAllgeier Does Last[{##}] do what you want or do I misunderstand? – Mr.Wizard – 2014-07-31T05:36:05.167

@Mr.Wizard, That does. I should have realized that. So it looks like putting ## in a list allows you to work with Last and Part. That is what I was missing. – Ben Allgeier – 2014-07-31T12:24:52.393

Thanks very much! Looks like I will be re-writing some code to take advantage of these features! – R Hall – 2012-06-09T02:42:21.107

I saw the pure functions and instantly knew who wrote the answer... – Brett Champion – 2012-06-09T03:02:26.230

@Brett I hope that's a good thing. :-) – Mr.Wizard – 2012-06-09T07:15:20.550

@Mr.Wizard, thanks for a very well explained answer. I found that one case is left unexplained: For a function like f[x__,y__] how would you distribute the arguments equally to both the variables x and y ? – sinner – 2016-09-20T21:32:18.640

@sinner As far as I can recall that is not available through pattern matching alone. The closest I can think of would be Repeated, i.e. something like f[x : Repeated[_, {3}], y : Repeated[_, {3}]] but that requires a specific number of arguments. I think one would need to do the splitting manually, e.g. f[x__] := {{x}[[;; #]], {x}[[# + 1 ;;]]} &[Length@{x}/2]. (See TakeDrop if you use version 10.2 or later) – Mr.Wizard – 2016-09-21T07:04:17.713

1

@Mr.Wizard I found a slightly better way to do the same on this link: f[a__,b__] /; (Length@{a} == Length@{b})] := "ok". Maybe you can include this in your answer for completeness. Thanks again!

– sinner – 2016-09-21T14:28:18.323

@sinner That is clever, and I feel a bit foolish for not thinking of it. Be aware that for large numbers of arguments this may become inefficient as it is brute-force checking n/2 alignments. – Mr.Wizard – 2016-09-21T15:31:38.037

Can _. be used without the default command? – Ion Sme – 2019-05-11T21:42:45.363

1

@IonSme _. needs a matching Default definition set, in case the argument is omitted. You can specify a local default using x_: default. If you want the optional argument to disappear from the right hand side if it is omitted on the left, you can use something like x : Repeated[_, {0, 1}] or x_ | ___. Perhaps also read (44084).

– Mr.Wizard – 2019-05-13T00:33:05.130

10

Just one supplementary remark regarding conditions on the argument list:

Functions with a variable number of arguments can be very useful, and patterns like x__ allow you to define them easily.

But while the number of arguments may not be fixed, there may still be restrictions or limits on the allowed number. This can be enforced by using a condition as shown here:

f[x__] := Total@Apply[Times, Partition[{x}, 2], 2] /; EvenQ[Length[{x}]]

In this example, f is intended to form pairs from the arguments, multiply the pairs and add them. That's why there is a condition appended to the definition with /; - it insures that the definition is not used unless an even number of arguments is supplied.

If the condition isn't met, the function returns unevaluated - unless you provide another definition for a function with the same name with a different condition.

Jens

Posted 2012-06-09T01:18:26.203

Reputation: 93 191

1

Related question: http://mathematica.stackexchange.com/q/1835/121

– Mr.Wizard – 2012-06-09T03:39:39.367

9

This may be close to what you want:

datasetAverage[inputDataList_] :=
  If[
    Equal @@ Map[Length, inputDataList] == False,
    Print["DatasetAverage: Data sample sizes do not match"],
    Mean[inputDataList]
  ]

Some sample inputs:

In[10]:= datasetAverage[{{1,2,3},{4,5,6},{7,8,9}}]
Out[10]= {4,5,6}

datasetAverage[{{1,2,3},{4,5,6,7}}]
DatasetAverage: Data sample sizes do not match

Edit

My earlier version of datasetAverage was defined using BlankSequence, but it did not make use of the fact that it can take an unspecified number of parameters. It is now rewritten using Blank.

Here is one version which uses BlankSequence:

datasetAverage2[inputDataList__] :=
  If[
    Equal @@ Map[Length, {inputDataList}] == False, 
    Print["DatasetAverage: Data sample sizes do not match"], 
    Mean[{inputDataList}]
  ]

The only difference is that I now enclose every occurrence of inputDataList in parentheses.

In[9]:= datasetAverage2[{1,2,3},{4,5,6},{7,8,9}]
Out[9]= {4,5,6}

Michael Wijaya

Posted 2012-06-09T01:18:26.203

Reputation: 2 157

Thank you! What allows for the multiple inputs? – R Hall – 2012-06-09T01:38:04.243

1@RHall I used the BlankSequence pattern, which stands for any sequence of one or more expressions. So now I can feed in a sequence of datasets. – Michael Wijaya – 2012-06-09T01:40:40.060

I'm rather confused by your edit. Why did you convert this to a single argument function? – Mr.Wizard – 2012-06-09T03:38:12.503

@Mr.Wizard I mixed up two ideas when I wrote up the answer. The original function does not actually require the use of BlankSequence. In fact, it fails if I evaluate datasetAverage[{1,2,3},{4,5,6}]. – Michael Wijaya – 2012-06-09T03:48:27.160

8

Mean already does what your DataSetAverage is expected to do. From docs Mean > More Information:

enter image description here

that is, Mean map-threads over sub-lists in its argument.

So

Mean[{{a, b, c}, {u, v, w}}]

gives

enter image description here

and

Mean[{{a, b, c}, {u, v, w, x}}]

throws the error

enter image description here

So, you can use something like

ClearAll[dtAvrg]; 
dtAvrg[lst__List] :=  Check[Mean[{lst}], "DatasetAverage: Data sample sizes do not match",  Mean::rectt]  

examples:

 dtAvrg[{a, b, c}, {u, v, w}, {x, y, z}] 

returns

enter image description here

and

 dtAvrg[{1, 2, 3}, {4, 5, 6}, {7, 8, 9}]

gives {4,5,6}.

For lists with unequal lengths,

dtAvrg[{a, b, c}, {u, v, w}, {x, y, z, t}] 

returns

Mean::rectt: Rectangular array expected at position 1 in Mean[{{a,b,c},{u,v,w},{x,y,z,t}}].
"DatasetAverage: Data sample sizes do not match"  

If you like you can Quiet the error message and display your message only:

 Quiet@dtAvrg[{a, b, c}, {x, y, z, t}] 
 (* gives:  "DatasetAverage: Data sample sizes do not match" *)   

kglr

Posted 2012-06-09T01:18:26.203

Reputation: 302 076

Great tip Thank you! – R Hall – 2012-06-09T10:54:01.740

+1 Your method is much cleaner than mine. It is good to know about Check too. – Michael Wijaya – 2012-06-09T17:27:58.177

1@MichaelWijaya, thank you for the vote. – kglr – 2012-06-09T17:36:59.453